Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 01c51ee

Browse files
authored
Merge pull request #3680 from erik-krogh/bad-code-sanitizer
JS: Add query to detect bad code sanitizers
2 parents 2342d3d + f0ec2eb commit 01c51ee

11 files changed

Lines changed: 374 additions & 24 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Placeholder
9+
</p>
10+
</overview>
11+
12+
<recommendation>
13+
<p>
14+
Placeholder
15+
</p>
16+
</recommendation>
17+
18+
<example>
19+
<p>
20+
Placeholder
21+
</p>
22+
23+
</example>
24+
25+
<references>
26+
<li>
27+
OWASP:
28+
<a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.
29+
</li>
30+
<li>
31+
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#Function_properties">Global functions</a>.
32+
</li>
33+
<li>
34+
MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function">Function constructor</a>.
35+
</li>
36+
</references>
37+
</qhelp>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @name Improper code sanitization
3+
* @description Escaping code as HTML does not provide protection against code-injection.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id js/bad-code-sanitization
8+
* @tags security
9+
* external/cwe/cwe-094
10+
* external/cwe/cwe-079
11+
* external/cwe/cwe-116
12+
*/
13+
14+
import javascript
15+
import semmle.javascript.security.dataflow.ImproperCodeSanitization::ImproperCodeSanitization
16+
import DataFlow::PathGraph
17+
private import semmle.javascript.heuristics.HeuristicSinks
18+
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
19+
20+
/**
21+
* Gets a type-tracked instance of `RemoteFlowSource` using type-tracker `t`.
22+
*/
23+
private DataFlow::SourceNode remoteFlow(DataFlow::TypeTracker t) {
24+
t.start() and
25+
result instanceof RemoteFlowSource
26+
or
27+
exists(DataFlow::TypeTracker t2 | result = remoteFlow(t2).track(t2, t))
28+
}
29+
30+
/**
31+
* Gets a type-tracked reference to a `RemoteFlowSource`.
32+
*/
33+
private DataFlow::SourceNode remoteFlow() { result = remoteFlow(DataFlow::TypeTracker::end()) }
34+
35+
/**
36+
* Gets a type-back-tracked instance of a code-injection sink using type-tracker `t`.
37+
*/
38+
private DataFlow::Node endsInCodeInjectionSink(DataFlow::TypeBackTracker t) {
39+
t.start() and
40+
(
41+
result instanceof CodeInjection::Sink
42+
or
43+
result instanceof HeuristicCodeInjectionSink and
44+
not result instanceof StringOps::ConcatenationRoot // the heuristic CodeInjection sink looks for string-concats, we are not interrested in those here.
45+
)
46+
or
47+
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, endsInCodeInjectionSink(t2)))
48+
}
49+
50+
/**
51+
* Gets a reference to to a data-flow node that ends in a code-injection sink.
52+
*/
53+
private DataFlow::Node endsInCodeInjectionSink() {
54+
result = endsInCodeInjectionSink(DataFlow::TypeBackTracker::end())
55+
}
56+
57+
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
58+
where
59+
cfg.hasFlowPath(source, sink) and
60+
// Basic detection of duplicate results with `js/code-injection`.
61+
not (
62+
sink.getNode().(StringOps::ConcatenationLeaf).getRoot() = endsInCodeInjectionSink() and
63+
remoteFlow().flowsTo(source.getNode().(DataFlow::InvokeNode).getAnArgument())
64+
)
65+
select sink.getNode(), source, sink, "$@ flows to here and is used to construct code.",
66+
source.getNode(), "Improperly sanitized value"

javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,12 @@ private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations
1717
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
1818
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
1919
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
20+
private import HeuristicSinks as Sinks
2021

21-
/**
22-
* A heuristic sink for data flow in a security query.
23-
*/
24-
abstract class HeuristicSink extends DataFlow::Node { }
22+
class HeuristicSink = Sinks::HeuristicSink;
2523

26-
private class HeuristicCodeInjectionSink extends HeuristicSink, CodeInjection::Sink {
27-
HeuristicCodeInjectionSink() {
28-
isAssignedTo(this, "$where")
29-
or
30-
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
31-
or
32-
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`
33-
or
34-
exists(string srcPattern |
35-
// function/lambda syntax anywhere
36-
srcPattern = "(?s).*function\\s*\\(.*\\).*" or
37-
srcPattern = "(?s).*(\\(.*\\)|[A-Za-z_]+)\\s?=>.*"
38-
|
39-
isConcatenatedWithString(this, srcPattern)
40-
)
41-
or
42-
// dynamic property name
43-
isConcatenatedWithStrings("(?is)[a-z]+\\[", this, "(?s)\\].*")
44-
}
45-
}
24+
private class HeuristicCodeInjectionSink extends Sinks::HeuristicCodeInjectionSink,
25+
CodeInjection::Sink { }
4626

4727
private class HeuristicCommandInjectionSink extends HeuristicSink, CommandInjection::Sink {
4828
HeuristicCommandInjectionSink() {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Provides heuristically recognized sinks for security queries.
3+
*/
4+
5+
import javascript
6+
private import SyntacticHeuristics
7+
8+
/**
9+
* A heuristic sink for data flow in a security query.
10+
*/
11+
abstract class HeuristicSink extends DataFlow::Node { }
12+
13+
/**
14+
* A heuristically recognized sink for `js/code-injection` vulnerabilities.
15+
*/
16+
class HeuristicCodeInjectionSink extends HeuristicSink {
17+
HeuristicCodeInjectionSink() {
18+
isAssignedTo(this, "$where")
19+
or
20+
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
21+
or
22+
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`
23+
or
24+
exists(string srcPattern |
25+
// function/lambda syntax anywhere
26+
srcPattern = "(?s).*function\\s*\\(.*\\).*" or
27+
srcPattern = "(?s).*(\\(.*\\)|[A-Za-z_]+)\\s?=>.*"
28+
|
29+
isConcatenatedWithString(this, srcPattern)
30+
)
31+
or
32+
// dynamic property name
33+
isConcatenatedWithStrings("(?is)[a-z]+\\[", this, "(?s)\\].*")
34+
}
35+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about improper code
3+
* sanitization.
4+
*
5+
* Note, for performance reasons: only import this file if
6+
* `ImproperCodeSanitization::Configuration` is needed, otherwise
7+
* `ImproperCodeSanitizationCustomizations` should be imported instead.
8+
*/
9+
10+
import javascript
11+
12+
/**
13+
* Classes and predicates for reasoning about improper code sanitization.
14+
*/
15+
module ImproperCodeSanitization {
16+
import ImproperCodeSanitizationCustomizations::ImproperCodeSanitization
17+
18+
/**
19+
* A taint-tracking configuration for reasoning about improper code sanitization vulnerabilities.
20+
*/
21+
class Configuration extends TaintTracking::Configuration {
22+
Configuration() { this = "ImproperCodeSanitization" }
23+
24+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
25+
26+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
27+
28+
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }
29+
}
30+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for reasoning about
3+
* improper code sanitization, as well as extension points for
4+
* adding your own.
5+
*/
6+
7+
import javascript
8+
9+
/**
10+
* Classes and predicates for reasoning about improper code sanitization.
11+
*/
12+
module ImproperCodeSanitization {
13+
/**
14+
* A data flow source for improper code sanitization.
15+
*/
16+
abstract class Source extends DataFlow::Node { }
17+
18+
/**
19+
* A data flow sink for improper code sanitization.
20+
*/
21+
abstract class Sink extends DataFlow::Node { }
22+
23+
/**
24+
* A sanitizer for improper code sanitization.
25+
*/
26+
abstract class Sanitizer extends DataFlow::Node { }
27+
28+
/**
29+
* A call to a HTML sanitizer seen as a source for improper code sanitization
30+
*/
31+
class HtmlSanitizerCallAsSource extends Source {
32+
HtmlSanitizerCallAsSource() { this instanceof HtmlSanitizerCall }
33+
}
34+
35+
/**
36+
* A call to `JSON.stringify()` seen as a source for improper code sanitization
37+
*/
38+
class JSONStringifyAsSource extends Source {
39+
JSONStringifyAsSource() { this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") }
40+
}
41+
42+
/**
43+
* A leaf in a string-concatenation, where the string-concatenation constructs code that looks like a function.
44+
*/
45+
class FunctionStringConstruction extends Sink, StringOps::ConcatenationLeaf {
46+
FunctionStringConstruction() {
47+
exists(StringOps::ConcatenationRoot root, int i |
48+
root.getOperand(i) = this and
49+
not exists(this.getStringValue())
50+
|
51+
exists(StringOps::ConcatenationLeaf functionLeaf |
52+
functionLeaf = root.getOperand(any(int j | j < i))
53+
|
54+
functionLeaf
55+
.getStringValue()
56+
.regexpMatch([".*function( )?([a-zA-Z0-9]+)?( )?\\(.*", ".*eval\\(.*",
57+
".*new Function\\(.*", "(^|.*[^a-zA-Z0-9])\\(.*\\)( )?=>.*"])
58+
)
59+
)
60+
}
61+
}
62+
63+
/**
64+
* A call to `String.prototype.replace` seen as a sanitizer for improper code sanitization.
65+
* All calls to replace that happens after the initial improper sanitization is seen as a sanitizer.
66+
*/
67+
class StringReplaceCallAsSanitizer extends Sanitizer, StringReplaceCall { }
68+
}

javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ nodes
6464
| angularjs.js:53:32:53:39 | location |
6565
| angularjs.js:53:32:53:46 | location.search |
6666
| angularjs.js:53:32:53:46 | location.search |
67+
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
68+
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
69+
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
70+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
71+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
6772
| express.js:7:24:7:69 | "return ... + "];" |
6873
| express.js:7:24:7:69 | "return ... + "];" |
6974
| express.js:7:44:7:62 | req.param("wobble") |
@@ -193,6 +198,10 @@ edges
193198
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
194199
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
195200
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
201+
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
202+
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
203+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
204+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
196205
| express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" |
197206
| express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" |
198207
| express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" |
@@ -261,6 +270,7 @@ edges
261270
| angularjs.js:47:16:47:30 | location.search | angularjs.js:47:16:47:23 | location | angularjs.js:47:16:47:30 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:47:16:47:23 | location | User-provided value |
262271
| angularjs.js:50:22:50:36 | location.search | angularjs.js:50:22:50:29 | location | angularjs.js:50:22:50:36 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:50:22:50:29 | location | User-provided value |
263272
| angularjs.js:53:32:53:46 | location.search | angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:53:32:53:39 | location | User-provided value |
273+
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` | bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` | $@ flows to here and is interpreted as code. | bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | User-provided value |
264274
| express.js:7:24:7:69 | "return ... + "];" | express.js:7:44:7:62 | req.param("wobble") | express.js:7:24:7:69 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:7:44:7:62 | req.param("wobble") | User-provided value |
265275
| express.js:9:34:9:79 | "return ... + "];" | express.js:9:54:9:72 | req.param("wobble") | express.js:9:34:9:79 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:9:54:9:72 | req.param("wobble") | User-provided value |
266276
| express.js:12:8:12:53 | "return ... + "];" | express.js:12:28:12:46 | req.param("wobble") | express.js:12:8:12:53 | "return ... + "];" | $@ flows to here and is interpreted as code. | express.js:12:28:12:46 | req.param("wobble") | User-provided value |

javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ nodes
6464
| angularjs.js:53:32:53:39 | location |
6565
| angularjs.js:53:32:53:46 | location.search |
6666
| angularjs.js:53:32:53:46 | location.search |
67+
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
68+
| bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
69+
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
70+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
71+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") |
6772
| eslint-escope-build.js:20:22:20:22 | c |
6873
| eslint-escope-build.js:20:22:20:22 | c |
6974
| eslint-escope-build.js:21:16:21:16 | c |
@@ -197,6 +202,10 @@ edges
197202
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
198203
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
199204
| angularjs.js:53:32:53:39 | location | angularjs.js:53:32:53:46 | location.search |
205+
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
206+
| bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) | bad-code-sanitization.js:54:14:54:67 | `(funct ... "))}))` |
207+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
208+
| bad-code-sanitization.js:54:44:54:62 | req.param("wobble") | bad-code-sanitization.js:54:29:54:63 | JSON.st ... bble")) |
200209
| eslint-escope-build.js:20:22:20:22 | c | eslint-escope-build.js:21:16:21:16 | c |
201210
| eslint-escope-build.js:20:22:20:22 | c | eslint-escope-build.js:21:16:21:16 | c |
202211
| eslint-escope-build.js:20:22:20:22 | c | eslint-escope-build.js:21:16:21:16 | c |

0 commit comments

Comments
 (0)