Skip to content

Commit 23908f9

Browse files
committed
remove flowpaths that has a returns without a matching call
1 parent 6e754c7 commit 23908f9

File tree

5 files changed

+50
-3
lines changed

5 files changed

+50
-3
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,15 @@ module UnsafeHtmlConstruction {
2828
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
2929
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
3030
}
31+
32+
// override to require that there is a path without unmatched return steps
33+
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
34+
super.hasFlowPath(source, sink) and
35+
requireMatchedReturn(source, sink)
36+
}
37+
38+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
39+
classFieldStep(pred, succ)
40+
}
3141
}
3242
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,28 @@ module UnsafeHtmlConstruction {
165165

166166
override string describe() { result = "Markdown rendering" }
167167
}
168+
169+
/**
170+
* A taint step from the write of a field in a constructor to a read of the same field in an instance method.
171+
*/
172+
predicate classFieldStep(DataFlow::Node pred, DataFlow::Node succ) {
173+
// flow-step from a property written in the constructor to a use in an instance method.
174+
// "simulates" client usage of a class, and regains some flow-steps lost by `requireMatchedReturn` below.
175+
exists(DataFlow::ClassNode clazz, string prop |
176+
DataFlow::thisNode(clazz.getConstructor().getFunction()).getAPropertyWrite(prop).getRhs() =
177+
pred and
178+
DataFlow::thisNode(clazz.getAnInstanceMethod().getFunction()).getAPropertyRead(prop) = succ
179+
)
180+
}
181+
182+
/**
183+
* Holds if there is a path without unmatched return steps from `source` to `sink`.
184+
*/
185+
predicate requireMatchedReturn(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
186+
exists(DataFlow::MidPathNode mid |
187+
source.getASuccessor*() = mid and
188+
sink = mid.getASuccessor() and
189+
mid.getPathSummary().hasReturn() = false
190+
)
191+
}
168192
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ nodes
1515
| main.js:21:47:21:47 | s |
1616
| main.js:22:34:22:34 | s |
1717
| main.js:22:34:22:34 | s |
18+
| main.js:46:17:46:17 | s |
19+
| main.js:47:21:47:21 | s |
20+
| main.js:52:65:52:73 | this.step |
21+
| main.js:52:65:52:73 | this.step |
22+
| main.js:57:41:57:41 | s |
23+
| main.js:57:41:57:41 | s |
24+
| main.js:58:20:58:20 | s |
1825
| typed.ts:1:39:1:39 | s |
1926
| typed.ts:1:39:1:39 | s |
2027
| typed.ts:2:29:2:29 | s |
@@ -47,6 +54,12 @@ edges
4754
| main.js:21:47:21:47 | s | main.js:22:34:22:34 | s |
4855
| main.js:21:47:21:47 | s | main.js:22:34:22:34 | s |
4956
| main.js:21:47:21:47 | s | main.js:22:34:22:34 | s |
57+
| main.js:46:17:46:17 | s | main.js:47:21:47:21 | s |
58+
| main.js:47:21:47:21 | s | main.js:52:65:52:73 | this.step |
59+
| main.js:47:21:47:21 | s | main.js:52:65:52:73 | this.step |
60+
| main.js:57:41:57:41 | s | main.js:58:20:58:20 | s |
61+
| main.js:57:41:57:41 | s | main.js:58:20:58:20 | s |
62+
| main.js:58:20:58:20 | s | main.js:46:17:46:17 | s |
5063
| typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s |
5164
| typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s |
5265
| typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s |
@@ -67,6 +80,6 @@ edges
6780
| main.js:12:49:12:49 | s | main.js:11:60:11:60 | s | main.js:12:49:12:49 | s | $@ based on $@ might later cause $@. | main.js:12:49:12:49 | s | XML parsing | main.js:11:60:11:60 | s | library input | main.js:16:21:16:35 | xml.cloneNode() | cross-site scripting |
6881
| main.js:12:49:12:49 | s | main.js:11:60:11:60 | s | main.js:12:49:12:49 | s | $@ based on $@ might later cause $@. | main.js:12:49:12:49 | s | XML parsing | main.js:11:60:11:60 | s | library input | main.js:17:48:17:50 | tmp | cross-site scripting |
6982
| main.js:22:34:22:34 | s | main.js:21:47:21:47 | s | main.js:22:34:22:34 | s | $@ based on $@ might later cause $@. | main.js:22:34:22:34 | s | Markdown rendering | main.js:21:47:21:47 | s | library input | main.js:23:53:23:56 | html | cross-site scripting |
83+
| main.js:52:65:52:73 | this.step | main.js:57:41:57:41 | s | main.js:52:65:52:73 | this.step | $@ based on $@ might later cause $@. | main.js:52:65:52:73 | this.step | HTML construction | main.js:57:41:57:41 | s | library input | main.js:52:54:52:85 | "<span> ... /span>" | cross-site scripting |
7084
| typed.ts:2:29:2:29 | s | typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s | $@ based on $@ might later cause $@. | typed.ts:2:29:2:29 | s | HTML construction | typed.ts:1:39:1:39 | s | library input | typed.ts:3:31:3:34 | html | cross-site scripting |
7185
| typed.ts:8:40:8:40 | s | typed.ts:6:43:6:43 | s | typed.ts:8:40:8:40 | s | $@ based on $@ might later cause $@. | typed.ts:8:40:8:40 | s | HTML construction | typed.ts:6:43:6:43 | s | library input | typed.ts:8:29:8:52 | "<span> ... /span>" | cross-site scripting |
72-
| typed.ts:17:29:17:29 | s | typed.ts:11:20:11:20 | s | typed.ts:17:29:17:29 | s | $@ based on $@ might later cause $@. | typed.ts:17:29:17:29 | s | HTML construction | typed.ts:11:20:11:20 | s | library input | typed.ts:18:31:18:34 | html | cross-site scripting |

javascript/ql/test/query-tests/Security/CWE-079/UnsafeHtmlConstruction/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Foo {
4444

4545
doXss() {
4646
// not called here, but still bad.
47-
document.querySelector("#class").innerHTML = "<span>" + this.step + "</span>"; // NOT OK - but not flagged [INCONSISTENCY]
47+
document.querySelector("#class").innerHTML = "<span>" + this.step + "</span>"; // NOT OK
4848
}
4949

5050
}

javascript/ql/test/query-tests/Security/CWE-079/UnsafeHtmlConstruction/typed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function id(s: string) {
1414

1515
export function notVulnerable() {
1616
const s = id("x");
17-
const html = "<span>" + s + "</span>"; // OK - but flagged due to step with unmatched call [INCONSISTENCY]
17+
const html = "<span>" + s + "</span>"; // OK
1818
document.body.innerHTML = html;
1919
}
2020

0 commit comments

Comments
 (0)