Skip to content

Commit 6494649

Browse files
committed
fix a number of FPs in js/exception-xss
1 parent 79811fc commit 6494649

File tree

4 files changed

+62
-19
lines changed

4 files changed

+62
-19
lines changed

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: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,39 @@ 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
20+
result = "indexOf" or
21+
result = "stringify" or
22+
result = "assign" or
23+
// fs methods. (The callback argument to the async functions are vulnerable, but its unlikely that the callback is the user-controlled part).
24+
result = "existsSync" or
25+
result = "exists" or
26+
result = "writeFileSync" or
27+
result = "writeFile" or
28+
result = "appendFile" or
29+
result = "appendFileSync" or
30+
result = "pick" or
31+
// log.info etc.
32+
result = "info" or
33+
result = "warn" or
34+
result = "error" or
35+
result = "join" or
36+
result = "val" or // $.val
37+
result = "parse" or // JSON.parse
38+
result = "push" or // Array.prototype.push
39+
result = "test" // RegExp.prototype.test
40+
}
41+
1542
/**
1643
* Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown.
1744
*/
1845
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()
46+
node = any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName())
47+
.getAnArgument()
2448
or
2549
node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument()
2650
}
@@ -38,6 +62,7 @@ module ExceptionXss {
3862
*/
3963
predicate canThrowSensitiveInformation(DataFlow::Node node) {
4064
not isUnlikelyToThrowSensitiveInformation(node) and
65+
not node instanceof Xss::Shared::Sink and // removes duplicates from js/xss.
4166
(
4267
// in the case of reflective calls the below ensures that both InvokeNodes have no known callee.
4368
forex(DataFlow::InvokeNode call | call.getAnArgument() = node | not exists(call.getACallee()))
@@ -79,15 +104,15 @@ module ExceptionXss {
79104
}
80105

81106
/**
82-
* Get the parameter in the callback that contains an error.
107+
* Get the parameter in the callback that contains an error.
83108
* In the current implementation this is always the first parameter.
84109
*/
85110
DataFlow::Node getErrorParam() { result = errorParameter }
86111
}
87112

88113
/**
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) => { ... })`.
114+
* Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to.
115+
* For example: `outerCall(foo, <pred>, bar, (<result>, val) => { ... })`.
91116
*/
92117
DataFlow::Node getCallbackErrorParam(DataFlow::Node pred) {
93118
exists(DataFlow::CallNode call, Callback callback |
@@ -101,8 +126,8 @@ module ExceptionXss {
101126
/**
102127
* Gets the data-flow node to which any exceptions thrown by
103128
* this expression will propagate.
104-
* This predicate adds, on top of `Expr::getExceptionTarget`, exceptions
105-
* propagated by callbacks.
129+
* This predicate adds, on top of `Expr::getExceptionTarget`, exceptions
130+
* propagated by callbacks.
106131
*/
107132
private DataFlow::Node getExceptionTarget(DataFlow::Node pred) {
108133
result = pred.asExpr().getExceptionTarget()

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@ module Shared {
5151
)
5252
}
5353
}
54+
55+
/**
56+
* A property read from a safe property is considered a sanitizer.
57+
*/
58+
class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node {
59+
SafePropertyReadSanitizer() {
60+
exists(PropAccess pacc | pacc = this.asExpr() |
61+
isSafeLocationProperty(pacc)
62+
or
63+
// `$(location.hash)` is a fairly common and safe idiom
64+
// (because `location.hash` always starts with `#`),
65+
// so we mark `hash` as safe for the purposes of this query
66+
pacc.getPropertyName() = "hash"
67+
or
68+
pacc.getPropertyName() = "length"
69+
)
70+
}
71+
}
5472
}
5573

5674
/** Provides classes and predicates for the DOM-based XSS query. */
@@ -269,6 +287,8 @@ module DomBasedXss {
269287
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
270288

271289
private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { }
290+
291+
private class SafePropertyReadSanitizer extends Sanitizer, Shared::SafePropertyReadSanitizer {}
272292
}
273293

274294
/** Provides classes and predicates for the reflected XSS query. */

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)