Skip to content

Commit 5bc1792

Browse files
authored
Merge pull request #665 from asger-semmle/js-property-concat-sanitizer
Approved by esben-semmle, xiemaisi
2 parents cf3a4ac + f4c8960 commit 5bc1792

File tree

10 files changed

+77
-22
lines changed

10 files changed

+77
-22
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, and no longer flags variables with leading underscore. |
3434
| Uncontrolled data used in path expression | Fewer false-positive results | This rule now recognizes the Express `root` option, which prevents path traversal. |
3535
| Useless assignment to property. | Fewer false-positive results | This rule now treats assignments with complex right-hand sides correctly. |
36+
| Unsafe dynamic method access | Fewer false-positive results | This rule no longer flags concatenated strings as unsafe method names. |
37+
| Unvalidated dynamic method call | More true-positive results | This rule now flags concatenated strings as unvalidated method names in more cases. |
3638

3739
## Changes to QL libraries
3840

javascript/ql/src/semmle/javascript/StringConcatenation.qll

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,35 @@ module StringConcatenation {
7575
* Holds if there is a taint step from `src` to `dst` through string concatenation.
7676
*/
7777
predicate taintStep(DataFlow::Node src, DataFlow::Node dst) { taintStep(src, dst, _, _) }
78+
79+
/**
80+
* Holds if `node` is the root of a concatenation tree, that is,
81+
* it is a concatenation operator that is not itself the immediate operand to
82+
* another concatenation operator.
83+
*/
84+
predicate isRoot(DataFlow::Node node) {
85+
exists(getAnOperand(node)) and
86+
not node = getAnOperand(_)
87+
}
88+
89+
/**
90+
* Gets the root of the concatenation tree in which `node` is an operand or operator.
91+
*/
92+
DataFlow::Node getRoot(DataFlow::Node node) {
93+
isRoot(node) and
94+
result = node
95+
or
96+
exists(DataFlow::Node operator |
97+
node = getAnOperand(operator) and
98+
result = getRoot(operator)
99+
)
100+
}
101+
102+
/**
103+
* Holds if `node` is a string concatenation that only acts as a string coercion.
104+
*/
105+
predicate isCoercion(DataFlow::Node node) {
106+
getNumOperand(node) = 2 and
107+
getOperand(node, _).asExpr().getStringValue() = ""
108+
}
78109
}

javascript/ql/src/semmle/javascript/security/dataflow/PropertyInjectionShared.qll

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@
66
import javascript
77

88
module PropertyInjection {
9-
/**
10-
* A data-flow node that sanitizes user-controlled property names that flow through it.
11-
*/
12-
abstract class Sanitizer extends DataFlow::Node { }
13-
14-
/**
15-
* Concatenation with a constant, acting as a sanitizer.
16-
*/
17-
private class ConcatSanitizer extends Sanitizer {
18-
ConcatSanitizer() { StringConcatenation::getAnOperand(this).asExpr() instanceof ConstantString }
19-
}
20-
219
/**
2210
* Holds if the methods of the given value are unsafe, such as `eval`.
2311
*/

javascript/ql/src/semmle/javascript/security/dataflow/RemotePropertyInjection.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module RemotePropertyInjection {
4242
override predicate isSanitizer(DataFlow::Node node) {
4343
super.isSanitizer(node) or
4444
node instanceof Sanitizer or
45-
node instanceof PropertyInjection::Sanitizer
45+
node = StringConcatenation::getRoot(any(ConstantString str).flow())
4646
}
4747
}
4848

javascript/ql/src/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccess.qll

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,12 @@ module UnsafeDynamicMethodAccess {
6060
}
6161

6262
override predicate isSanitizer(DataFlow::Node node) {
63-
super.isSanitizer(node) or
64-
node instanceof Sanitizer or
65-
node instanceof PropertyInjection::Sanitizer
63+
super.isSanitizer(node)
64+
or
65+
node instanceof Sanitizer
66+
or
67+
exists(StringConcatenation::getOperand(node, _)) and
68+
not StringConcatenation::isCoercion(node)
6669
}
6770

6871
/**

javascript/ql/src/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCall.qll

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ module UnvalidatedDynamicMethodCall {
6868
sink.(Sink).getFlowLabel() = label
6969
}
7070

71-
override predicate isSanitizer(DataFlow::Node nd) {
72-
super.isSanitizer(nd) or
73-
nd instanceof PropertyInjection::Sanitizer
74-
}
71+
override predicate isSanitizer(DataFlow::Node nd) { super.isSanitizer(nd) }
7572

7673
override predicate isAdditionalFlowStep(
7774
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ nodes
2323
| tst.js:11:7:11:18 | message.name |
2424
| tst.js:15:5:15:14 | window[ev] |
2525
| tst.js:15:12:15:13 | ev |
26+
| tst.js:21:5:21:29 | window[ ... e.name] |
27+
| tst.js:21:12:21:28 | '' + message.name |
28+
| tst.js:21:17:21:23 | message |
29+
| tst.js:21:17:21:28 | message.name |
2630
edges
2731
| example.js:9:37:9:38 | ev | example.js:10:30:10:31 | ev |
2832
| example.js:10:9:10:37 | message | example.js:13:12:13:18 | message |
@@ -36,6 +40,7 @@ edges
3640
| tst.js:4:9:4:37 | message | tst.js:5:12:5:18 | message |
3741
| tst.js:4:9:4:37 | message | tst.js:6:16:6:22 | message |
3842
| tst.js:4:9:4:37 | message | tst.js:11:7:11:13 | message |
43+
| tst.js:4:9:4:37 | message | tst.js:21:17:21:23 | message |
3944
| tst.js:4:19:4:37 | JSON.parse(ev.data) | tst.js:4:9:4:37 | message |
4045
| tst.js:4:30:4:31 | ev | tst.js:4:30:4:36 | ev.data |
4146
| tst.js:4:30:4:36 | ev.data | tst.js:4:19:4:37 | JSON.parse(ev.data) |
@@ -46,9 +51,13 @@ edges
4651
| tst.js:11:7:11:13 | message | tst.js:11:7:11:18 | message.name |
4752
| tst.js:11:7:11:18 | message.name | tst.js:11:5:11:19 | f[message.name] |
4853
| tst.js:15:12:15:13 | ev | tst.js:15:5:15:14 | window[ev] |
54+
| tst.js:21:12:21:28 | '' + message.name | tst.js:21:5:21:29 | window[ ... e.name] |
55+
| tst.js:21:17:21:23 | message | tst.js:21:17:21:28 | message.name |
56+
| tst.js:21:17:21:28 | message.name | tst.js:21:12:21:28 | '' + message.name |
4957
#select
5058
| example.js:13:5:13:24 | window[message.name] | example.js:9:37:9:38 | ev | example.js:13:5:13:24 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | example.js:9:37:9:38 | ev | user-controlled value |
5159
| tst.js:5:5:5:24 | window[message.name] | tst.js:3:37:3:38 | ev | tst.js:5:5:5:24 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
5260
| tst.js:6:9:6:28 | window[message.name] | tst.js:3:37:3:38 | ev | tst.js:6:9:6:28 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
5361
| tst.js:11:5:11:19 | f[message.name] | tst.js:3:37:3:38 | ev | tst.js:11:5:11:19 | f[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
5462
| tst.js:15:5:15:14 | window[ev] | tst.js:3:37:3:38 | ev | tst.js:15:5:15:14 | window[ev] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |
63+
| tst.js:21:5:21:29 | window[ ... e.name] | tst.js:3:37:3:38 | ev | tst.js:21:5:21:29 | window[ ... e.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value |

javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/tst.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,10 @@ window.addEventListener('message', (ev) => {
1313
obj[message.name](message.payload); // OK - may crash, but no code execution involved
1414

1515
window[ev](ev); // NOT OK
16+
17+
window[configData() + ' ' + message.name](message.payload); // OK - concatenation restricts choice of methods
18+
19+
window[configData() + message.name](message.payload); // OK - concatenation restricts choice of methods
20+
21+
window['' + message.name](message.payload); // NOT OK - coercion does not restrict choice of methods
1622
});

javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.expected

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ nodes
4646
| tst.js:26:11:26:14 | name |
4747
| tst.js:28:7:28:15 | obj[name] |
4848
| tst.js:28:11:28:14 | name |
49+
| tst.js:34:9:34:24 | key |
50+
| tst.js:34:15:34:24 | "$" + name |
51+
| tst.js:34:21:34:24 | name |
52+
| tst.js:35:5:35:12 | obj[key] |
53+
| tst.js:35:5:35:12 | obj[key] |
54+
| tst.js:35:9:35:11 | key |
55+
| tst.js:37:7:37:14 | obj[key] |
56+
| tst.js:37:11:37:13 | key |
4957
| tst.js:47:39:47:40 | ev |
5058
| tst.js:48:9:48:39 | name |
5159
| tst.js:48:16:48:34 | JSON.parse(ev.data) |
@@ -78,6 +86,7 @@ edges
7886
| tst.js:7:9:7:39 | name | tst.js:21:11:21:14 | name |
7987
| tst.js:7:9:7:39 | name | tst.js:26:11:26:14 | name |
8088
| tst.js:7:9:7:39 | name | tst.js:28:11:28:14 | name |
89+
| tst.js:7:9:7:39 | name | tst.js:34:21:34:24 | name |
8190
| tst.js:7:16:7:34 | JSON.parse(ev.data) | tst.js:7:16:7:39 | JSON.pa ... a).name |
8291
| tst.js:7:16:7:39 | JSON.pa ... a).name | tst.js:7:9:7:39 | name |
8392
| tst.js:7:27:7:28 | ev | tst.js:7:27:7:33 | ev.data |
@@ -101,6 +110,13 @@ edges
101110
| tst.js:26:11:26:14 | name | tst.js:26:7:26:15 | obj[name] |
102111
| tst.js:26:11:26:14 | name | tst.js:26:7:26:15 | obj[name] |
103112
| tst.js:28:11:28:14 | name | tst.js:28:7:28:15 | obj[name] |
113+
| tst.js:34:9:34:24 | key | tst.js:35:9:35:11 | key |
114+
| tst.js:34:9:34:24 | key | tst.js:37:11:37:13 | key |
115+
| tst.js:34:15:34:24 | "$" + name | tst.js:34:9:34:24 | key |
116+
| tst.js:34:21:34:24 | name | tst.js:34:15:34:24 | "$" + name |
117+
| tst.js:35:9:35:11 | key | tst.js:35:5:35:12 | obj[key] |
118+
| tst.js:35:9:35:11 | key | tst.js:35:5:35:12 | obj[key] |
119+
| tst.js:37:11:37:13 | key | tst.js:37:7:37:14 | obj[key] |
104120
| tst.js:47:39:47:40 | ev | tst.js:48:27:48:28 | ev |
105121
| tst.js:48:9:48:39 | name | tst.js:49:19:49:22 | name |
106122
| tst.js:48:16:48:34 | JSON.parse(ev.data) | tst.js:48:16:48:39 | JSON.pa ... a).name |
@@ -128,4 +144,7 @@ edges
128144
| tst.js:26:7:26:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:26:7:26:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
129145
| tst.js:26:7:26:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:26:7:26:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
130146
| tst.js:28:7:28:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:28:7:28:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
147+
| tst.js:35:5:35:12 | obj[key] | tst.js:6:39:6:40 | ev | tst.js:35:5:35:12 | obj[key] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
148+
| tst.js:35:5:35:12 | obj[key] | tst.js:6:39:6:40 | ev | tst.js:35:5:35:12 | obj[key] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
149+
| tst.js:37:7:37:14 | obj[key] | tst.js:6:39:6:40 | ev | tst.js:37:7:37:14 | obj[key] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled |
131150
| tst.js:50:5:50:6 | fn | tst.js:47:39:47:40 | ev | tst.js:50:5:50:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:47:39:47:40 | ev | user-controlled |

javascript/ql/test/query-tests/Security/CWE-754/tst.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
}
3333

3434
let key = "$" + name;
35-
obj[key](); // NOT OK, but not flagged
35+
obj[key](); // NOT OK
3636
if (typeof obj[key] === 'function')
37-
obj[key](); // OK
37+
obj[key](); // OK - but still flagged
3838

3939
if (typeof fn === 'function') {
4040
fn.apply(obj); // OK

0 commit comments

Comments
 (0)