Skip to content

Commit 5272810

Browse files
authored
Merge pull request github#12826 from asgerf/js/more-call-graph-steps
JS: Improvements to type-tracking through 'extend' and 'this'
2 parents fdd975b + 13b1e97 commit 5272810

File tree

13 files changed

+129
-15
lines changed

13 files changed

+129
-15
lines changed

javascript/ql/lib/semmle/javascript/Extend.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,12 @@ private class FunctionalExtendCallShallow extends ExtendCall {
157157
}
158158

159159
/**
160-
* A taint propagating data flow edge from the objects flowing into an extend call to its return value
160+
* A value-preserving data flow edge from the objects flowing into an extend call to its return value
161161
* and to the source of the destination object.
162+
*
163+
* Since all object properties are preserved, we model this as a value-preserving step.
162164
*/
163-
private class ExtendCallTaintStep extends TaintTracking::SharedTaintStep {
165+
private class ExtendCallStep extends PreCallGraphStep {
164166
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
165167
exists(ExtendCall extend |
166168
pred = extend.getASourceOperand() and succ = extend.getDestinationOperand().getALocalSource()

javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,10 @@ private predicate basicFlowStepNoBarrier(
806806
callStep(pred, succ) and
807807
summary = PathSummary::call()
808808
or
809+
// Implied receiver flow
810+
CallGraph::impliedReceiverStep(pred, succ) and
811+
summary = PathSummary::call()
812+
or
809813
// Flow out of function
810814
returnStep(pred, succ) and
811815
summary = PathSummary::return()

javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,22 +241,26 @@ module CallGraph {
241241
)
242242
}
243243

244-
private predicate shouldTrackObjectWithMethods(DataFlow::SourceNode node) {
244+
private DataFlow::FunctionNode getAMethodOnPlainObject(DataFlow::SourceNode node) {
245245
(
246246
(
247247
node instanceof DataFlow::ObjectLiteralNode
248248
or
249249
node instanceof DataFlow::FunctionNode
250250
) and
251-
node.getAPropertySource() instanceof DataFlow::FunctionNode
251+
result = node.getAPropertySource()
252252
or
253-
exists(node.(DataFlow::ObjectLiteralNode).getPropertyGetter(_))
253+
result = node.(DataFlow::ObjectLiteralNode).getPropertyGetter(_)
254254
or
255-
exists(node.(DataFlow::ObjectLiteralNode).getPropertySetter(_))
255+
result = node.(DataFlow::ObjectLiteralNode).getPropertySetter(_)
256256
) and
257257
not node.getTopLevel().isExterns()
258258
}
259259

260+
private predicate shouldTrackObjectWithMethods(DataFlow::SourceNode node) {
261+
exists(getAMethodOnPlainObject(node))
262+
}
263+
260264
/**
261265
* Gets a step summary for tracking object literals.
262266
*
@@ -273,4 +277,22 @@ module CallGraph {
273277
or
274278
StepSummary::step(getAnAllocationSiteRef(node), result, objectWithMethodsStep())
275279
}
280+
281+
/**
282+
* Holds if `pred` is assumed to flow to `succ` because a method is stored on an object that is assumed
283+
* to be the receiver of calls to that method.
284+
*
285+
* For example, object literal below is assumed to flow to the receiver of the `foo` function:
286+
* ```js
287+
* let obj = {};
288+
* obj.foo = function() {}
289+
* ```
290+
*/
291+
cached
292+
predicate impliedReceiverStep(DataFlow::SourceNode pred, DataFlow::SourceNode succ) {
293+
exists(DataFlow::SourceNode host |
294+
pred = getAnAllocationSiteRef(host) and
295+
succ = getAMethodOnPlainObject(host).getReceiver()
296+
)
297+
}
276298
}

javascript/ql/lib/semmle/javascript/dataflow/internal/StepSummary.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ private module Cached {
9494
DataFlow::localFieldStep(pred, succ) and
9595
summary = LevelStep()
9696
or
97+
// Implied flow of host object into 'this' of a method
98+
CallGraph::impliedReceiverStep(pred, succ) and
99+
summary = CallStep()
100+
or
97101
exists(string prop |
98102
basicStoreStep(pred, succ, prop) and
99103
summary = StoreStep(prop)

javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,22 @@ class Configuration extends TaintTracking::Configuration {
5555
)
5656
}
5757

58+
override predicate isSanitizerEdge(
59+
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl
60+
) {
61+
// Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving
62+
// step because it preserves all properties, but the destination is not actually Object.prototype.
63+
exists(ExtendCall call |
64+
pred = call.getASourceOperand() and
65+
(
66+
succ = call.getDestinationOperand().getALocalSource()
67+
or
68+
succ = call
69+
) and
70+
lbl instanceof ObjectPrototype
71+
)
72+
}
73+
5874
override predicate isAdditionalFlowStep(
5975
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
6076
) {

javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeJQueryPluginCustomizations.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ module UnsafeJQueryPlugin {
3131
*/
3232
abstract class Sanitizer extends DataFlow::Node { }
3333

34+
/**
35+
* The receiver of a function, seen as a sanitizer.
36+
*
37+
* Plugins often do `$(this)` to coerce an existing DOM element to a jQuery object.
38+
*/
39+
private class ThisSanitizer extends Sanitizer instanceof DataFlow::ThisNode { }
40+
3441
/**
3542
* An argument that may act as an HTML fragment rather than a CSS selector, as a sink for remote unsafe jQuery plugins.
3643
*/
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Improved the call graph to better handle the case where a function is stored on
5+
a plain object and subsequently copied to a new host object via an `extend` call.

javascript/ql/test/query-tests/Security/CWE-020/UntrustedDataToExternalAPI/UntrustedDataToExternalAPI.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ nodes
3636
| tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted |
3737
| tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted |
3838
| tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted |
39+
| tst-UntrustedDataToExternalAPI.js:41:7:41:8 | {} |
40+
| tst-UntrustedDataToExternalAPI.js:41:7:41:8 | {} |
3941
| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} |
4042
| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} |
4143
| tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted |
@@ -83,6 +85,8 @@ edges
8385
| tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) | tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] |
8486
| tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) | tst-UntrustedDataToExternalAPI.js:24:20:24:42 | [JSON.p ... usted)] |
8587
| tst-UntrustedDataToExternalAPI.js:24:32:24:40 | untrusted | tst-UntrustedDataToExternalAPI.js:24:21:24:41 | JSON.pa ... rusted) |
88+
| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | tst-UntrustedDataToExternalAPI.js:41:7:41:8 | {} |
89+
| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | tst-UntrustedDataToExternalAPI.js:41:7:41:8 | {} |
8690
| tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} |
8791
| tst-UntrustedDataToExternalAPI.js:42:8:42:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} |
8892
| tst-UntrustedDataToExternalAPI.js:43:8:43:16 | untrusted | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} |
@@ -101,4 +105,5 @@ edges
101105
| tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:30:13:30:30 | getDeepUntrusted() | Call to external-lib() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name |
102106
| tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:33:14:33:22 | untrusted | Call to external-lib.get.[callback].[param 'res'].send() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name |
103107
| tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:34:34:34:42 | untrusted | Call to external-lib.get.[callback].[param 'req'].app.locals.something.foo() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name |
108+
| tst-UntrustedDataToExternalAPI.js:41:7:41:8 | {} | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:41:7:41:8 | {} | Call to lodash.merge() [param 0] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name |
104109
| tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | tst-UntrustedDataToExternalAPI.js:41:11:45:1 | {\\n x ... usted\\n} | Call to lodash.merge() [param 1] with untrusted data from $@. | tst-UntrustedDataToExternalAPI.js:3:17:3:27 | window.name | window.name |

javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,10 @@ nodes
11191119
| tst.js:494:18:494:30 | location.hash |
11201120
| tst.js:494:18:494:40 | locatio ... bstr(1) |
11211121
| tst.js:494:18:494:40 | locatio ... bstr(1) |
1122+
| tst.js:501:33:501:63 | decodeU ... n.hash) |
1123+
| tst.js:501:33:501:63 | decodeU ... n.hash) |
1124+
| tst.js:501:43:501:62 | window.location.hash |
1125+
| tst.js:501:43:501:62 | window.location.hash |
11221126
| typeahead.js:20:13:20:45 | target |
11231127
| typeahead.js:20:22:20:45 | documen ... .search |
11241128
| typeahead.js:20:22:20:45 | documen ... .search |
@@ -2271,6 +2275,10 @@ edges
22712275
| tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) |
22722276
| tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) |
22732277
| tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) |
2278+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
2279+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
2280+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
2281+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
22742282
| typeahead.js:20:13:20:45 | target | typeahead.js:21:12:21:17 | target |
22752283
| typeahead.js:20:22:20:45 | documen ... .search | typeahead.js:20:13:20:45 | target |
22762284
| typeahead.js:20:22:20:45 | documen ... .search | typeahead.js:20:13:20:45 | target |
@@ -2559,6 +2567,7 @@ edges
25592567
| tst.js:486:22:486:24 | url | tst.js:471:13:471:36 | documen ... .search | tst.js:486:22:486:24 | url | Cross-site scripting vulnerability due to $@. | tst.js:471:13:471:36 | documen ... .search | user-provided value |
25602568
| tst.js:491:23:491:45 | locatio ... bstr(1) | tst.js:491:23:491:35 | location.hash | tst.js:491:23:491:45 | locatio ... bstr(1) | Cross-site scripting vulnerability due to $@. | tst.js:491:23:491:35 | location.hash | user-provided value |
25612569
| tst.js:494:18:494:40 | locatio ... bstr(1) | tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) | Cross-site scripting vulnerability due to $@. | tst.js:494:18:494:30 | location.hash | user-provided value |
2570+
| tst.js:501:33:501:63 | decodeU ... n.hash) | tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) | Cross-site scripting vulnerability due to $@. | tst.js:501:43:501:62 | window.location.hash | user-provided value |
25622571
| typeahead.js:25:18:25:20 | val | typeahead.js:20:22:20:45 | documen ... .search | typeahead.js:25:18:25:20 | val | Cross-site scripting vulnerability due to $@. | typeahead.js:20:22:20:45 | documen ... .search | user-provided value |
25632572
| v-html.vue:2:8:2:23 | v-html=tainted | v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted | Cross-site scripting vulnerability due to $@. | v-html.vue:6:42:6:58 | document.location | user-provided value |
25642573
| various-concat-obfuscations.js:4:4:4:31 | "<div>" ... </div>" | various-concat-obfuscations.js:2:16:2:39 | documen ... .search | various-concat-obfuscations.js:4:4:4:31 | "<div>" ... </div>" | Cross-site scripting vulnerability due to $@. | various-concat-obfuscations.js:2:16:2:39 | documen ... .search | user-provided value |

javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,10 @@ nodes
11311131
| tst.js:494:18:494:30 | location.hash |
11321132
| tst.js:494:18:494:40 | locatio ... bstr(1) |
11331133
| tst.js:494:18:494:40 | locatio ... bstr(1) |
1134+
| tst.js:501:33:501:63 | decodeU ... n.hash) |
1135+
| tst.js:501:33:501:63 | decodeU ... n.hash) |
1136+
| tst.js:501:43:501:62 | window.location.hash |
1137+
| tst.js:501:43:501:62 | window.location.hash |
11341138
| typeahead.js:9:28:9:30 | loc |
11351139
| typeahead.js:9:28:9:30 | loc |
11361140
| typeahead.js:9:28:9:30 | loc |
@@ -2333,6 +2337,10 @@ edges
23332337
| tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) |
23342338
| tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) |
23352339
| tst.js:494:18:494:30 | location.hash | tst.js:494:18:494:40 | locatio ... bstr(1) |
2340+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
2341+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
2342+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
2343+
| tst.js:501:43:501:62 | window.location.hash | tst.js:501:33:501:63 | decodeU ... n.hash) |
23362344
| typeahead.js:9:28:9:30 | loc | typeahead.js:10:16:10:18 | loc |
23372345
| typeahead.js:9:28:9:30 | loc | typeahead.js:10:16:10:18 | loc |
23382346
| typeahead.js:9:28:9:30 | loc | typeahead.js:10:16:10:18 | loc |

0 commit comments

Comments
 (0)