Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 50a015c

Browse files
committed
JS: Move $() sink into separate dataflow config
1 parent 4cc7138 commit 50a015c

4 files changed

Lines changed: 103 additions & 71 deletions

File tree

javascript/ql/src/Security/CWE-079/Xss.ql

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ import javascript
1515
import semmle.javascript.security.dataflow.DomBasedXss::DomBasedXss
1616
import DataFlow::PathGraph
1717

18-
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
19-
where cfg.hasFlowPath(source, sink)
18+
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
19+
where
20+
(
21+
cfg instanceof HtmlInjectionConfiguration or
22+
cfg instanceof JQuerySelectorInjectionConfiguration
23+
) and
24+
cfg.hasFlowPath(source, sink)
2025
select sink.getNode(), source, sink,
2126
sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
2227
"user-provided value"

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

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,23 @@ import javascript
88
module DomBasedXss {
99
import DomBasedXssCustomizations::DomBasedXss
1010

11+
/**
12+
* DEPRECATED. Use `HtmlInjectionConfiguration` or `JQuerySelectorInjectionConfiguration`.
13+
*/
14+
deprecated class Configuration = HtmlInjectionConfiguration;
15+
1116
/**
1217
* A taint-tracking configuration for reasoning about XSS.
1318
*/
14-
class Configuration extends TaintTracking::Configuration {
15-
Configuration() { this = "DomBasedXss" }
19+
class HtmlInjectionConfiguration extends TaintTracking::Configuration {
20+
HtmlInjectionConfiguration() { this = "HtmlInjection" }
1621

1722
override predicate isSource(DataFlow::Node source) { source instanceof Source }
1823

19-
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
24+
override predicate isSink(DataFlow::Node sink) {
25+
sink instanceof Sink and
26+
not sink instanceof JQuerySelectorSink // Handled by JQuerySelectorInjectionConfiguration below
27+
}
2028

2129
override predicate isSanitizer(DataFlow::Node node) {
2230
super.isSanitizer(node)
@@ -28,59 +36,68 @@ module DomBasedXss {
2836
guard instanceof SanitizerGuard
2937
}
3038

31-
override predicate isAdditionalStoreStep(
32-
DataFlow::Node pred, DataFlow::SourceNode succ, string prop
33-
) {
34-
exists(DataFlow::PropRead read |
35-
pred = read.getBase() and
36-
succ = read and
37-
read.getPropertyName() = "hash" and
38-
prop = urlSuffixPseudoProperty()
39-
)
39+
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
40+
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
41+
}
42+
}
43+
44+
/**
45+
* A taint-tracking configuration for reasoning about injection into the jQuery `$` function
46+
* or similar, where the interpretation of the input string depends on its first character.
47+
*
48+
* Values are only considered tainted if they can start with the `<` character.
49+
*/
50+
class JQuerySelectorInjectionConfiguration extends TaintTracking::Configuration {
51+
JQuerySelectorInjectionConfiguration() { this = "JQuerySelectorInjection" }
52+
53+
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
54+
// Reuse any source not derived from location
55+
source instanceof Source and
56+
not source = DOM::locationRef() and
57+
label.isTaint()
58+
or
59+
source = DOM::locationSource() and
60+
label.isData() // Require transformation before reaching sink
61+
or
62+
source = DOM::locationRef().getAPropertyRead(["hash", "search"]) and
63+
label.isData() // Require transformation before reaching sink
64+
}
65+
66+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
67+
sink instanceof JQuerySelectorSink and label.isTaint()
4068
}
4169

42-
override predicate isAdditionalLoadStoreStep(
43-
DataFlow::Node pred, DataFlow::Node succ, string predProp, string succProp
70+
override predicate isAdditionalFlowStep(
71+
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
72+
DataFlow::FlowLabel succlbl
4473
) {
45-
exists(DataFlow::PropRead read |
46-
pred = read.getBase() and
47-
succ = read and
48-
read.getPropertyName() = "hash" and
49-
predProp = "hash" and
50-
succProp = urlSuffixPseudoProperty()
74+
exists(TaintTracking::AdditionalTaintStep step |
75+
step.step(pred, succ) and
76+
predlbl.isData() and
77+
succlbl.isTaint()
5178
)
5279
}
5380

54-
override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
55-
exists(DataFlow::MethodCallNode call |
56-
call.getMethodName() = ["substr", "substring", "slice"] and
57-
not call.getArgument(0).getIntValue() = 0 and
58-
pred = call.getReceiver() and
59-
succ = call and
60-
prop = urlSuffixPseudoProperty()
61-
)
62-
or
63-
exists(DataFlow::MethodCallNode call |
64-
call.getMethodName() = "exec" and pred = call.getArgument(0)
65-
or
66-
call.getMethodName() = "match" and pred = call.getReceiver()
67-
|
68-
succ = call and
69-
prop = urlSuffixPseudoProperty()
70-
)
81+
override predicate isSanitizer(DataFlow::Node node) {
82+
super.isSanitizer(node)
7183
or
72-
exists(StringSplitCall split |
73-
split.getSeparator() = ["#", "?"] and
74-
pred = split.getBaseString() and
75-
succ = split.getASubstringRead(1) and
76-
prop = urlSuffixPseudoProperty()
77-
)
84+
node instanceof Sanitizer
85+
}
86+
87+
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
88+
guard instanceof SanitizerGuard
7889
}
7990

8091
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
8192
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
93+
or
94+
// Avoid stepping from location -> location.hash, as the .hash is already treated as a source
95+
// (with a different flow label)
96+
exists(DataFlow::PropRead read |
97+
read = DOM::locationRef().getAPropertyRead(["hash", "search"]) and
98+
pred = read.getBase() and
99+
succ = read
100+
)
82101
}
83102
}
84-
85-
private string urlSuffixPseudoProperty() { result = "$UrlSuffix$" }
86103
}

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

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,9 @@ module DomBasedXss {
153153
class LibrarySink extends Sink, DataFlow::ValueNode {
154154
LibrarySink() {
155155
// call to a jQuery method that interprets its argument as HTML
156-
exists(JQuery::MethodCall call | call.interpretsArgumentAsHtml(this) |
157-
// either the argument is always interpreted as HTML
158-
not call.interpretsArgumentAsSelector(this)
159-
or
160-
// or it doesn't start with something other than `<`, and so at least
161-
// _may_ be interpreted as HTML
162-
not exists(DataFlow::Node prefix, string strval |
163-
isPrefixOfJQueryHtmlString(this, prefix) and
164-
strval = prefix.getStringValue() and
165-
not strval = "" and
166-
not strval.regexpMatch("\\s*<.*")
167-
) and
168-
not DOM::locationRef().flowsTo(this)
156+
exists(JQuery::MethodCall call |
157+
call.interpretsArgumentAsHtml(this) and
158+
not call.interpretsArgumentAsSelector(this) // Handled by `JQuerySelectorSink`
169159
)
170160
or
171161
// call to an Angular method that interprets its argument as HTML
@@ -192,16 +182,41 @@ module DomBasedXss {
192182
* HTML by a jQuery method.
193183
*/
194184
predicate isPrefixOfJQueryHtmlString(DataFlow::Node htmlString, DataFlow::Node prefix) {
195-
any(JQuery::MethodCall call).interpretsArgumentAsHtml(htmlString) and
196-
prefix = htmlString
185+
prefix = getAPrefixOfJQuerySelectorString(htmlString)
186+
}
187+
188+
/**
189+
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
190+
* HTML by a jQuery method.
191+
*/
192+
private DataFlow::Node getAPrefixOfJQuerySelectorString(DataFlow::Node htmlString) {
193+
any(JQuery::MethodCall call).interpretsArgumentAsSelector(htmlString) and
194+
result = htmlString
197195
or
198-
exists(DataFlow::Node pred | isPrefixOfJQueryHtmlString(htmlString, pred) |
199-
prefix = StringConcatenation::getFirstOperand(pred)
196+
exists(DataFlow::Node pred | pred = getAPrefixOfJQuerySelectorString(htmlString) |
197+
result = StringConcatenation::getFirstOperand(pred)
200198
or
201-
prefix = pred.getAPredecessor()
199+
result = pred.getAPredecessor()
202200
)
203201
}
204202

203+
/**
204+
* An argument to the jQuery `$` function, which is interpreted as either a selector
205+
* or as an HTML string depending on its first character.
206+
*/
207+
class JQuerySelectorSink extends Sink {
208+
JQuerySelectorSink() {
209+
exists(JQuery::MethodCall call |
210+
call.interpretsArgumentAsHtml(this) and
211+
call.interpretsArgumentAsSelector(this) and
212+
// If a prefix of the string is known, it must start with '<' or be an empty string
213+
forall(string strval | strval = getAPrefixOfJQuerySelectorString(this).getStringValue() |
214+
strval.regexpMatch("(?s)\\s*<.*|")
215+
)
216+
)
217+
}
218+
}
219+
205220
/**
206221
* An expression whose value is interpreted as HTML or CSS
207222
* and may be inserted into the DOM.
@@ -350,11 +365,6 @@ module DomBasedXss {
350365
exists(PropAccess pacc | pacc = this.asExpr() |
351366
isSafeLocationProperty(pacc)
352367
or
353-
// `$(location.hash)` is a fairly common and safe idiom
354-
// (because `location.hash` always starts with `#`),
355-
// so we mark `hash` as safe for the purposes of this query
356-
pacc.getPropertyName() = "hash"
357-
or
358368
pacc.getPropertyName() = "length"
359369
)
360370
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import semmle.javascript.security.dataflow.DomBasedXss::DomBasedXss
1616
import DataFlow::PathGraph
1717
import semmle.javascript.heuristics.AdditionalSources
1818

19-
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
19+
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
2020
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
2121
select sink.getNode(), source, sink,
2222
sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),

0 commit comments

Comments
 (0)