Skip to content

Commit 3a7845e

Browse files
authored
Merge pull request #2653 from erik-krogh/exceptionFPs
Approved by esbena
2 parents cc73352 + 162c19c commit 3a7845e

File tree

8 files changed

+92
-21
lines changed

8 files changed

+92
-21
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ abstract class LoggerCall extends DataFlow::CallNode {
1717
/**
1818
* Gets a log level name that is used in RFC5424, `npm`, `console`.
1919
*/
20-
private string getAStandardLoggerMethodName() {
20+
string getAStandardLoggerMethodName() {
2121
result = "crit" or
2222
result = "debug" or
2323
result = "error" or

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,6 @@ module DomBasedXss {
2121
override predicate isSanitizer(DataFlow::Node node) {
2222
super.isSanitizer(node)
2323
or
24-
exists(PropAccess pacc | pacc = node.asExpr() |
25-
isSafeLocationProperty(pacc)
26-
or
27-
// `$(location.hash)` is a fairly common and safe idiom
28-
// (because `location.hash` always starts with `#`),
29-
// so we mark `hash` as safe for the purposes of this query
30-
pacc.getPropertyName() = "hash"
31-
)
32-
or
3324
node instanceof Sanitizer
3425
}
3526
}

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

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,33 @@ module ExceptionXss {
1212
import Xss as Xss
1313
private import semmle.javascript.dataflow.InferredTypes
1414

15+
/**
16+
* Gets the name of a method that does not leak taint from its arguments if an exception is thrown by the method.
17+
*/
18+
private string getAnUnlikelyToThrowMethodName() {
19+
result = "getElementById" or // document.getElementById
20+
result = "indexOf" or // String.prototype.indexOf
21+
result = "assign" or // Object.assign
22+
result = "pick" or // _.pick
23+
result = getAStandardLoggerMethodName() or // log.info etc.
24+
result = "val" or // $.val
25+
result = "parse" or // JSON.parse
26+
result = "stringify" or // JSON.stringify
27+
result = "test" or // RegExp.prototype.test
28+
result = "setItem" or // localStorage.setItem
29+
result = "existsSync" or
30+
// the "fs" methods are a mix of "this is safe" and "you have bigger problems".
31+
exists(ExternalMemberDecl decl | decl.hasQualifiedName("fs", result)) or
32+
// Array methods are generally exception safe.
33+
exists(ExternalMemberDecl decl | decl.hasQualifiedName("Array", result))
34+
}
35+
1536
/**
1637
* Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown.
1738
*/
1839
private predicate isUnlikelyToThrowSensitiveInformation(DataFlow::Node node) {
19-
node = any(DataFlow::CallNode call | call.getCalleeName() = "getElementById").getAnArgument()
20-
or
21-
node = any(DataFlow::CallNode call | call.getCalleeName() = "indexOf").getAnArgument()
22-
or
23-
node = any(DataFlow::CallNode call | call.getCalleeName() = "stringify").getAnArgument()
40+
node = any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName())
41+
.getAnArgument()
2442
or
2543
node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument()
2644
}
@@ -38,6 +56,7 @@ module ExceptionXss {
3856
*/
3957
predicate canThrowSensitiveInformation(DataFlow::Node node) {
4058
not isUnlikelyToThrowSensitiveInformation(node) and
59+
not node instanceof Xss::Shared::Sink and // removes duplicates from js/xss.
4160
(
4261
// in the case of reflective calls the below ensures that both InvokeNodes have no known callee.
4362
forex(DataFlow::InvokeNode call | call.getAnArgument() = node | not exists(call.getACallee()))
@@ -79,15 +98,15 @@ module ExceptionXss {
7998
}
8099

81100
/**
82-
* Get the parameter in the callback that contains an error.
101+
* Gets the parameter in the callback that contains an error.
83102
* In the current implementation this is always the first parameter.
84103
*/
85104
DataFlow::Node getErrorParam() { result = errorParameter }
86105
}
87106

88107
/**
89-
* Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to.
90-
* For example: `outerCall(foo, <pred>, bar, (<result>, val) => { ... })`.
108+
* Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to.
109+
* For example: `outerCall(foo, <pred>, bar, (<result>, val) => { ... })`.
91110
*/
92111
DataFlow::Node getCallbackErrorParam(DataFlow::Node pred) {
93112
exists(DataFlow::CallNode call, Callback callback |
@@ -101,8 +120,8 @@ module ExceptionXss {
101120
/**
102121
* Gets the data-flow node to which any exceptions thrown by
103122
* this expression will propagate.
104-
* This predicate adds, on top of `Expr::getExceptionTarget`, exceptions
105-
* propagated by callbacks.
123+
* This predicate adds, on top of `Expr::getExceptionTarget`, exceptions
124+
* propagated by callbacks.
106125
*/
107126
private DataFlow::Node getExceptionTarget(DataFlow::Node pred) {
108127
result = pred.asExpr().getExceptionTarget()

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,24 @@ module DomBasedXss {
259259
}
260260
}
261261

262+
/**
263+
* A property read from a safe property is considered a sanitizer.
264+
*/
265+
class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node {
266+
SafePropertyReadSanitizer() {
267+
exists(PropAccess pacc | pacc = this.asExpr() |
268+
isSafeLocationProperty(pacc)
269+
or
270+
// `$(location.hash)` is a fairly common and safe idiom
271+
// (because `location.hash` always starts with `#`),
272+
// so we mark `hash` as safe for the purposes of this query
273+
pacc.getPropertyName() = "hash"
274+
or
275+
pacc.getPropertyName() = "length"
276+
)
277+
}
278+
}
279+
262280
/**
263281
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
264282
* XSS vulnerabilities.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ nodes
99
| etherpad.js:9:16:9:53 | req.que ... e + ")" |
1010
| etherpad.js:11:12:11:19 | response |
1111
| etherpad.js:11:12:11:19 | response |
12+
| exception-xss.js:190:12:190:24 | req.params.id |
13+
| exception-xss.js:190:12:190:24 | req.params.id |
14+
| exception-xss.js:190:12:190:24 | req.params.id |
1215
| formatting.js:4:9:4:29 | evil |
1316
| formatting.js:4:16:4:29 | req.query.evil |
1417
| formatting.js:4:16:4:29 | req.query.evil |
@@ -77,6 +80,7 @@ edges
7780
| etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" |
7881
| etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" |
7982
| etherpad.js:9:16:9:53 | req.que ... e + ")" | etherpad.js:9:5:9:53 | response |
83+
| exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id |
8084
| formatting.js:4:9:4:29 | evil | formatting.js:6:43:6:46 | evil |
8185
| formatting.js:4:9:4:29 | evil | formatting.js:7:49:7:52 | evil |
8286
| formatting.js:4:16:4:29 | req.query.evil | formatting.js:4:9:4:29 | evil |
@@ -131,6 +135,7 @@ edges
131135
#select
132136
| 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 |
133137
| 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 |
138+
| exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value |
134139
| 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 |
135140
| 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 |
136141
| partial.js:10:14:10:18 | x + y | partial.js:13:42:13:48 | req.url | partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value |

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
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+
| exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value |
23
| 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 |
34
| 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 |
45
| partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value |

javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,34 @@ app.get('/user/:id', function (req, res) {
183183
}
184184
$('myId').html(res); // NOT OK!
185185
});
186-
});
186+
});
187+
188+
app.get('/user/:id', function (req, res) {
189+
try {
190+
res.send(req.params.id);
191+
} catch(err) {
192+
res.send(err); // OK (the above `res.send()` is already reported by js/xss)
193+
}
194+
});
195+
196+
var fs = require("fs");
197+
198+
(function () {
199+
var foo = document.location.search;
200+
201+
try {
202+
// A series of functions does not throw tainted exceptions.
203+
Object.assign(foo, foo)
204+
_.pick(foo, foo);
205+
[foo, foo].join(join);
206+
$.val(foo);
207+
JSON.parse(foo);
208+
/bla/.test(foo);
209+
console.log(foo);
210+
log.info(foo);
211+
localStorage.setItem(foo);
212+
} catch (e) {
213+
$('myId').html(e); // OK
214+
}
215+
216+
})();

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,10 @@ function basicExceptions() {
318318
function handlebarsSafeString() {
319319
return new Handlebars.SafeString(location); // NOT OK!
320320
}
321+
322+
function test2() {
323+
var target = document.location.search
324+
325+
// OK
326+
$('myId').html(target.length)
327+
}

0 commit comments

Comments
 (0)