Skip to content

Commit 9e6f9c2

Browse files
authored
Merge pull request github#11709 from github/jhelie/add-shell-command-injection
ATM: add boosted version for `ShellCommandInjectionFromEnvironment` query
2 parents 976b040 + fec7ea6 commit 9e6f9c2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+163951
-5
lines changed

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointCharacteristics.qll

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,28 @@ private class NosqlInjectionSinkCharacteristic extends EndpointCharacteristic {
278278
}
279279
}
280280

281+
/**
282+
* Endpoints identified as "ShellCommandInjectionFromEnvironmentSink" by the standard JavaScript libraries are
283+
* ShellCommandInjectionFromEnvironment sinks with maximal confidence.
284+
*/
285+
private class ShellCommandInjectionFromEnvironmentSinkCharacteristic extends EndpointCharacteristic {
286+
ShellCommandInjectionFromEnvironmentSinkCharacteristic() {
287+
this = "ShellCommandInjectionFromEnvironmentSink"
288+
}
289+
290+
override predicate appliesToEndpoint(DataFlow::Node n) {
291+
n instanceof ShellCommandInjectionFromEnvironment::Sink
292+
}
293+
294+
override predicate hasImplications(
295+
EndpointType endpointClass, boolean isPositiveIndicator, float confidence
296+
) {
297+
endpointClass instanceof ShellCommandInjectionFromEnvironmentSinkType and
298+
isPositiveIndicator = true and
299+
confidence = maximalConfidence()
300+
}
301+
}
302+
281303
/*
282304
* Characteristics that are indicative of not being a sink of any type, and have historically been used to select
283305
* negative samples for training.

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointTypes.qll

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ newtype TEndpointType =
1010
TXssSinkType() or
1111
TNosqlInjectionSinkType() or
1212
TSqlInjectionSinkType() or
13-
TTaintedPathSinkType()
13+
TTaintedPathSinkType() or
14+
TShellCommandInjectionFromEnvironmentSinkType()
1415

1516
/** A class that can be predicted by endpoint scoring models. */
1617
abstract class EndpointType extends TEndpointType {
@@ -60,3 +61,11 @@ class TaintedPathSinkType extends EndpointType, TTaintedPathSinkType {
6061

6162
override int getEncoding() { result = 4 }
6263
}
64+
65+
/** The `ShellCommandInjectionFromEnvironmentSink` class that can be predicted by endpoint scoring models. */
66+
class ShellCommandInjectionFromEnvironmentSinkType extends EndpointType,
67+
TShellCommandInjectionFromEnvironmentSinkType {
68+
override string getDescription() { result = "ShellCommandInjectionFromEnvironmentSink" }
69+
70+
override int getEncoding() { result = 5 }
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* For internal use only.
3+
*
4+
* A taint-tracking configuration for reasoning about command-injection
5+
* vulnerabilities.
6+
* Defines shared code used by the ShellCommandInjectionFromEnvironment boosted query.
7+
*/
8+
9+
private import semmle.javascript.heuristics.SyntacticHeuristics
10+
private import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentCustomizations::ShellCommandInjectionFromEnvironment as ShellCommandInjectionFromEnvironment
11+
import AdaptiveThreatModeling
12+
13+
class ShellCommandInjectionFromEnvironmentAtmConfig extends AtmConfig {
14+
ShellCommandInjectionFromEnvironmentAtmConfig() {
15+
this = "ShellCommandInjectionFromEnvironmentAtmConfig"
16+
}
17+
18+
override predicate isKnownSource(DataFlow::Node source) {
19+
source instanceof ShellCommandInjectionFromEnvironment::Source
20+
}
21+
22+
override EndpointType getASinkEndpointType() {
23+
result instanceof ShellCommandInjectionFromEnvironmentSinkType
24+
}
25+
26+
override predicate isSanitizer(DataFlow::Node node) {
27+
super.isSanitizer(node) or
28+
node instanceof ShellCommandInjectionFromEnvironment::Sanitizer
29+
}
30+
}

javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/DebugResultInclusion.ql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ private import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjecti
1717
private import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
1818
private import experimental.adaptivethreatmodeling.XssATM as XssAtm
1919
private import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomAtm
20+
private import experimental.adaptivethreatmodeling.ShellCommandInjectionFromEnvironmentATM as ShellCommandInjectionFromEnvironmentAtm
2021

2122
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
2223
query instanceof NosqlInjectionQuery and
@@ -33,6 +34,11 @@ string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
3334
or
3435
query instanceof XssThroughDomQuery and
3536
result = any(XssThroughDomAtm::XssThroughDomAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
37+
or
38+
query instanceof ShellCommandInjectionFromEnvironmentQuery and
39+
result =
40+
any(ShellCommandInjectionFromEnvironmentAtm::ShellCommandInjectionFromEnvironmentAtmConfig cfg)
41+
.getAReasonSinkExcluded(sinkCandidate)
3642
}
3743

3844
pragma[inline]

javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/ExtractEndpointDataTraining.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ private import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjecti
1515
private import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
1616
private import experimental.adaptivethreatmodeling.XssATM as XssAtm
1717
private import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomAtm
18+
private import experimental.adaptivethreatmodeling.ShellCommandInjectionFromEnvironmentATM as ShellCommandInjectionFromEnvironmentAtm
1819

1920
/**
2021
* Gets the set of featureName-featureValue pairs for each endpoint in the training set.
@@ -217,6 +218,10 @@ DataFlow::Configuration getDataFlowCfg(Query query) {
217218
query instanceof XssQuery and result instanceof XssAtm::DomBasedXssAtmConfig
218219
or
219220
query instanceof XssThroughDomQuery and result instanceof XssThroughDomAtm::XssThroughDomAtmConfig
221+
or
222+
query instanceof ShellCommandInjectionFromEnvironmentQuery and
223+
result instanceof
224+
ShellCommandInjectionFromEnvironmentAtm::ShellCommandInjectionFromEnvironmentAtmConfig
220225
}
221226

222227
// TODO: Delete this once we are no longer surfacing `hasFlowFromSource`.

javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/ExtractEndpointMapping.ql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAt
99
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
1010
import experimental.adaptivethreatmodeling.XssATM as XssAtm
1111
import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomAtm
12+
import experimental.adaptivethreatmodeling.ShellCommandInjectionFromEnvironmentATM as ShellCommandInjectionFromEnvironmentAtm
1213
import experimental.adaptivethreatmodeling.AdaptiveThreatModeling
1314

1415
from string queryName, AtmConfig c, EndpointType e
@@ -26,6 +27,10 @@ where
2627
queryName = "Xss" and c instanceof XssAtm::DomBasedXssAtmConfig
2728
or
2829
queryName = "XssThroughDom" and c instanceof XssThroughDomAtm::XssThroughDomAtmConfig
30+
or
31+
queryName = "ShellCommandInjectionFromEnvironment" and
32+
c instanceof
33+
ShellCommandInjectionFromEnvironmentAtm::ShellCommandInjectionFromEnvironmentAtmConfig
2934
) and
3035
e = c.getASinkEndpointType()
3136
select queryName, e.getEncoding() as label

javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/Queries.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ newtype TQuery =
99
TSqlInjectionQuery() or
1010
TTaintedPathQuery() or
1111
TXssQuery() or
12-
TXssThroughDomQuery()
12+
TXssThroughDomQuery() or
13+
TShellCommandInjectionFromEnvironmentQuery()
1314

1415
abstract class Query extends TQuery {
1516
abstract string getName();
@@ -36,3 +37,8 @@ class XssQuery extends Query, TXssQuery {
3637
class XssThroughDomQuery extends Query, TXssThroughDomQuery {
3738
override string getName() { result = "XssThroughDom" }
3839
}
40+
41+
class ShellCommandInjectionFromEnvironmentQuery extends Query,
42+
TShellCommandInjectionFromEnvironmentQuery {
43+
override string getName() { result = "ShellCommandInjectionFromEnvironment" }
44+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
2+
# Shell command built from environment values (experimental)
3+
4+
Dynamically constructing a shell command with values from the
5+
local environment, such as file paths, may inadvertently
6+
change the meaning of the shell command.
7+
8+
Such changes can occur when an environment value contains
9+
characters that the shell interprets in a special way, for instance
10+
quotes and spaces.
11+
12+
This can result in the shell command misbehaving, or even
13+
allowing a malicious user to execute arbitrary commands on the system.
14+
15+
Note: This CodeQL query is an experimental query. Experimental queries generate alerts using machine learning. They might include more false positives but they will improve over time.
16+
17+
## Recommendation
18+
19+
If possible, use hard-coded string literals to specify the
20+
shell command to run, and provide the dynamic arguments to the shell
21+
command separately to avoid interpretation by the shell.
22+
23+
Alternatively, if the shell command must be constructed
24+
dynamically, then add code to ensure that special characters in
25+
environment values do not alter the shell command unexpectedly.
26+
27+
## Example
28+
29+
The following example shows a dynamically constructed shell
30+
command that recursively removes a temporary directory that is located
31+
next to the currently executing JavaScript file. Such utilities are
32+
often found in custom build scripts.
33+
34+
```javascript
35+
var cp = require("child_process"),
36+
path = require("path");
37+
function cleanupTemp() {
38+
let cmd = "rm -rf " + path.join(__dirname, "temp");
39+
cp.execSync(cmd); // BAD
40+
}
41+
```
42+
43+
The shell command will, however, fail to work as intended if the
44+
absolute path of the script's directory contains spaces. In that
45+
case, the shell command will interpret the absolute path as multiple
46+
paths, instead of a single path.
47+
48+
For instance, if the absolute path of
49+
the temporary directory is "`/home/username/important project/temp`", then the shell command will recursively delete
50+
`"/home/username/important"` and `"project/temp"`,
51+
where the latter path gets resolved relative to the working directory
52+
of the JavaScript process.
53+
54+
Even worse, although less likely, a malicious user could
55+
provide the path `"/home/username/; cat /etc/passwd #/important
56+
project/temp"` in order to execute the command `"cat
57+
/etc/passwd"`.
58+
59+
To avoid such potentially catastrophic behaviors, provide the
60+
directory as an argument that does not get interpreted by a
61+
shell:
62+
63+
```javascript
64+
var cp = require("child_process"),
65+
path = require("path");
66+
function cleanupTemp() {
67+
let cmd = "rm",
68+
args = ["-rf", path.join(__dirname, "temp")];
69+
cp.execFileSync(cmd, args); // GOOD
70+
}
71+
```
72+
73+
74+
## References
75+
* OWASP: [Command Injection](https://www.owasp.org/index.php/Command_Injection)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* For internal use only.
3+
*
4+
* @name Shell command built from environment values
5+
* @description Building a shell command string with values from the enclosing
6+
* environment may cause subtle bugs or vulnerabilities.
7+
* @kind path-problem
8+
* @scored
9+
* @problem.severity warning
10+
* @security-severity 6.3
11+
* @precision high
12+
* @id js/ml-powered/shell-command-injection-from-environment
13+
* @tags experimental security
14+
* correctness
15+
* security
16+
* external/cwe/cwe-078
17+
* external/cwe/cwe-088
18+
*/
19+
20+
import javascript
21+
import experimental.adaptivethreatmodeling.ShellCommandInjectionFromEnvironmentATM
22+
import ATM::ResultsInfo
23+
import DataFlow::PathGraph
24+
25+
from AtmConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score
26+
where cfg.hasBoostedFlowPath(source, sink, score)
27+
select sink.getNode(), source, sink,
28+
"(Experimental) This shell command depends on $@. Identified using machine learning.",
29+
source.getNode(), "an uncontrolled value", score
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# DOM text reinterpreted as HTML (experimental)
2+
3+
Extracting text from a DOM node and interpreting it as HTML can lead to a cross-site scripting vulnerability.
4+
5+
A webpage with this vulnerability reads text from the DOM, and afterwards adds the text as HTML to the DOM. Using text from the DOM as HTML effectively unescapes the text, and thereby invalidates any escaping done on the text. If an attacker is able to control the safe sanitized text, then this vulnerability can be exploited to perform a cross-site scripting attack.
6+
7+
Note: This CodeQL query is an experimental query. Experimental queries generate alerts using machine learning. They might include more false positives but they will improve over time.
8+
9+
## Recommendation
10+
11+
To guard against cross-site scripting, consider using contextual output encoding/escaping before writing text to the page, or one of the other solutions that are mentioned in the References section below.
12+
13+
## Example
14+
15+
The following example shows a webpage using a `data-target` attribute
16+
to select and manipulate a DOM element using the JQuery library. In the example, the
17+
`data-target` attribute is read into the `target` variable, and the
18+
`$` function is then supposed to use the `target` variable as a CSS
19+
selector to determine which element should be manipulated.
20+
21+
```javascript
22+
$("button").click(function () {
23+
var target = $(this).attr("data-target");
24+
$(target).hide();
25+
});
26+
```
27+
28+
However, if an attacker can control the `data-target` attribute,
29+
then the value of `target` can be used to cause the `$` function
30+
to execute arbitrary JavaScript.
31+
32+
The above vulnerability can be fixed by using `$.find` instead of `$`.
33+
The `$.find` function will only interpret `target` as a CSS selector
34+
and never as HTML, thereby preventing an XSS attack.
35+
36+
```javascript
37+
$("button").click(function () {
38+
var target = $(this).attr("data-target");
39+
$.find(target).hide();
40+
});
41+
```
42+
43+
## References
44+
* OWASP: [DOM based XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html)
45+
* OWASP: [(Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
46+
* OWASP [DOM Based XSS](https://owasp.org/www-community/attacks/DOM_Based_XSS)
47+
* OWASP [Types of Cross-Site Scripting](https://owasp.org/www-community/Types_of_Cross-Site_Scripting)
48+
* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting)

0 commit comments

Comments
 (0)