Skip to content

Commit bbdf6b0

Browse files
author
Esben Sparre Andreasen
committed
JS: mark PrintfStyleCall as a taint step
1 parent c058b91 commit bbdf6b0

File tree

7 files changed

+57
-1
lines changed

7 files changed

+57
-1
lines changed

javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,27 @@ module TaintTracking {
408408
}
409409
}
410410

411+
/**
412+
* A taint propagating data flow edge arising from string formatting.
413+
*/
414+
private class StringFormattingTaintStep extends AdditionalTaintStep {
415+
416+
PrintfStyleCall call;
417+
418+
StringFormattingTaintStep() {
419+
this = call and
420+
call.returnsFormatted()
421+
}
422+
423+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
424+
succ = this and (
425+
pred = call.getFormatString()
426+
or
427+
pred = call.getFormatArgument(_)
428+
)
429+
}
430+
}
431+
411432
/**
412433
* A taint propagating data flow edge arising from JSON unparsing.
413434
*/

javascript/ql/src/semmle/javascript/frameworks/StringFormatters.qll

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,23 @@ abstract class PrintfStyleCall extends DataFlow::CallNode {
1717
* Gets the ith argument to the format string.
1818
*/
1919
abstract DataFlow::Node getFormatArgument(int i);
20+
21+
/**
22+
* Holds if this call returns the formatted string.
23+
*/
24+
abstract predicate returnsFormatted();
2025
}
2126

2227
private class LibraryFormatter extends PrintfStyleCall {
2328

2429
int formatIndex;
2530

31+
boolean returns;
32+
2633
LibraryFormatter() {
2734
// built-in Node.js functions
2835
exists (string mod, string meth |
36+
returns = false and
2937
mod = "console" and (
3038
(
3139
meth = "debug" or
@@ -40,6 +48,7 @@ private class LibraryFormatter extends PrintfStyleCall {
4048
meth = "assert" and formatIndex = 1
4149
)
4250
or
51+
returns = true and
4352
mod = "util" and (
4453
(meth = "format" or meth = "log") and formatIndex = 0
4554
or
@@ -53,7 +62,7 @@ private class LibraryFormatter extends PrintfStyleCall {
5362
this = DataFlow::globalVarRef(mod).getAMemberCall(meth)
5463
)
5564
or
56-
(
65+
returns = true and (
5766
// https://www.npmjs.com/package/printf
5867
this = DataFlow::moduleImport("printf").getACall() and
5968
formatIndex in [0..1]
@@ -91,4 +100,9 @@ private class LibraryFormatter extends PrintfStyleCall {
91100
i >= 0 and
92101
result = getArgument(formatIndex + 1 + i)
93102
}
103+
104+
override predicate returnsFormatted() {
105+
returns = true
106+
}
107+
94108
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
| ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value |
22
| etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value |
3+
| formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
4+
| formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
35
| promises.js:6:25:6:25 | x | Cross-site scripting vulnerability due to $@. | promises.js:5:44:5:57 | req.query.data | user-provided value |
46
| tst2.js:7:12:7:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:6:9:6:9 | p | user-provided value |
57
| tst2.js:8:12:8:12 | r | Cross-site scripting vulnerability due to $@. | tst2.js:6:12:6:15 | q: r | user-provided value |

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ edges
66
| etherpad.js:9:16:9:47 | req.que ... esponse | etherpad.js:9:16:9:53 | req.que ... e + ")" |
77
| etherpad.js:9:16:9:53 | req.que ... e + ")" | etherpad.js:9:5:9:53 | response |
88
| etherpad.js:11:3:11:3 | response | etherpad.js:11:12:11:19 | response |
9+
| formatting.js:4:9:4:29 | evil | formatting.js:6:43:6:46 | evil |
10+
| formatting.js:4:9:4:29 | evil | formatting.js:7:49:7:52 | evil |
11+
| formatting.js:4:16:4:29 | req.query.evil | formatting.js:4:9:4:29 | evil |
12+
| formatting.js:6:43:6:46 | evil | formatting.js:6:14:6:47 | util.fo ... , evil) |
13+
| formatting.js:7:49:7:52 | evil | formatting.js:7:14:7:53 | require ... , evil) |
914
| promises.js:5:3:5:59 | new Pro ... .data)) | promises.js:6:11:6:11 | x |
1015
| promises.js:5:44:5:57 | req.query.data | promises.js:5:3:5:59 | new Pro ... .data)) |
1116
| promises.js:5:44:5:57 | req.query.data | promises.js:6:11:6:11 | x |
@@ -18,6 +23,8 @@ edges
1823
#select
1924
| ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value |
2025
| etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value |
26+
| formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
27+
| formatting.js:7:14:7:53 | require ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
2128
| promises.js:6:25:6:25 | x | promises.js:5:44:5:57 | req.query.data | promises.js:6:25:6:25 | x | Cross-site scripting vulnerability due to $@. | promises.js:5:44:5:57 | req.query.data | user-provided value |
2229
| promises.js:6:25:6:25 | x | promises.js:5:44:5:57 | req.query.data | promises.js:6:25:6:25 | x | Cross-site scripting vulnerability due to $@. | promises.js:5:44:5:57 | req.query.data | user-provided value |
2330
| tst2.js:7:12:7:12 | p | tst2.js:6:9:6:9 | p | tst2.js:7:12:7:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:6:9:6:9 | p | user-provided value |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
| ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value |
2+
| formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
3+
| formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
24
| promises.js:6:25:6:25 | x | Cross-site scripting vulnerability due to $@. | promises.js:5:44:5:57 | req.query.data | user-provided value |
35
| tst2.js:7:12:7:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:6:9:6:9 | p | user-provided value |
46
| tst2.js:8:12:8:12 | r | Cross-site scripting vulnerability due to $@. | tst2.js:6:12:6:15 | q: r | user-provided value |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ WARNING: Type XssDataFlowConfiguration has been deprecated and may be removed in
33
WARNING: Type XssDataFlowConfiguration has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:20,6-30)
44
WARNING: Predicate flowsFrom has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:21,11-20)
55
| ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value |
6+
| formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
7+
| formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
68
| promises.js:6:25:6:25 | x | Cross-site scripting vulnerability due to $@. | promises.js:5:44:5:57 | req.query.data | user-provided value |
79
| tst2.js:7:12:7:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:6:9:6:9 | p | user-provided value |
810
| tst2.js:8:12:8:12 | r | Cross-site scripting vulnerability due to $@. | tst2.js:6:12:6:15 | q: r | user-provided value |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var express = require('express');
2+
3+
express().get('/user/', function(req, res) {
4+
var evil = req.query.evil;
5+
res.send(console.log("<div>%s</div>", evil)); // OK (returns undefined)
6+
res.send(util.format("<div>%s</div>", evil)); // NOT OK
7+
res.send(require("printf")("<div>%s</div>", evil)); // NOT OK
8+
});

0 commit comments

Comments
 (0)