Skip to content

Commit c133362

Browse files
authored
Merge pull request #910 from xiemaisi/js/regexp-taint
Approved by esben-semmle
2 parents 583358b + 6ce77ea commit c133362

File tree

9 files changed

+315
-622
lines changed

9 files changed

+315
-622
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- asynchronous code, for example [a-sync-waterfall](https://www.npmjs.com/package/a-sync-waterfall)
1010
* File classification has been improved to recognize additional generated files, for example files from [HTML Tidy](html-tidy.org).
1111

12-
* The taint tracking library now recognizes flow through persistent storage, class fields, and callbacks in certain cases. This may give more results for the security queries.
12+
* The taint tracking library now recognizes flow through persistent storage, class fields, and callbacks in certain cases. Handling of regular expressions has also been improved. This may give more results for the security queries.
1313

1414
* Type inference for function calls has been improved. This may give additional results for queries that rely on type inference.
1515

@@ -33,9 +33,11 @@
3333

3434
| **Query** | **Expected impact** | **Change** |
3535
|--------------------------------------------|------------------------------|------------------------------------------------------------------------------|
36-
| Client-side cross-site scripting | More true-positive results, fewer false-positive results. | This rule now recognizes WinJS functions that are vulnerable to HTML injection, and no longer flags certain safe uses of jQuery. |
36+
| Client-side cross-site scripting | More true-positive results, fewer false-positive results. | This rule now recognizes WinJS functions that are vulnerable to HTML injection. It no longer flags certain safe uses of jQuery, and recognizes custom sanitizers. |
3737
| Hard-coded credentials | Fewer false-positive results | This rule no longer flag the empty string as a hardcoded username. |
3838
| Insecure randomness | More results | This rule now flags insecure uses of `crypto.pseudoRandomBytes`. |
39+
| Reflected cross-site scripting | Fewer false-positive results. | This rule now recognizes custom sanitizers. |
40+
| Stored cross-site scripting | Fewer false-positive results. | This rule now recognizes custom sanitizers. |
3941
| Uncontrolled data used in network request | More results | This rule now recognizes host values that are vulnerable to injection. |
4042
| Unused parameter | Fewer false-positive results | This rule no longer flags parameters with leading underscore. |
4143
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, and no longer flags variables with leading underscore. |

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,27 @@ module TaintTracking {
473473
}
474474
}
475475

476+
/**
477+
* A taint-propagating data flow edge from the first (and only) argument in a call to
478+
* `RegExp.prototype.exec` to its result.
479+
*/
480+
private class RegExpExecTaintStep extends AdditionalTaintStep {
481+
DataFlow::MethodCallNode self;
482+
483+
RegExpExecTaintStep() {
484+
this = self and
485+
self.getReceiver().analyze().getAType() = TTRegExp() and
486+
self.getMethodName() = "exec" and
487+
self.getNumArgument() = 1
488+
}
489+
490+
491+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
492+
pred = self.getArgument(0) and
493+
succ = this
494+
}
495+
}
496+
476497
/**
477498
* A taint propagating data flow edge arising from JSON unparsing.
478499
*/

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

Lines changed: 1 addition & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,9 @@
44
*/
55

66
import javascript
7-
import semmle.javascript.security.dataflow.RemoteFlowSources
8-
import semmle.javascript.frameworks.jQuery
97

108
module DomBasedXss {
11-
/**
12-
* A data flow source for XSS vulnerabilities.
13-
*/
14-
abstract class Source extends DataFlow::Node { }
15-
16-
/**
17-
* A data flow sink for XSS vulnerabilities.
18-
*/
19-
abstract class Sink extends DataFlow::Node {
20-
/**
21-
* Gets the kind of vulnerability to report in the alert message.
22-
*
23-
* Defaults to `Cross-site scripting`, but may be overriden for sinks
24-
* that do not allow script injection, but injection of other undesirable HTML elements.
25-
*/
26-
string getVulnerabilityKind() { result = "Cross-site scripting" }
27-
}
28-
29-
/**
30-
* A sanitizer for XSS vulnerabilities.
31-
*/
32-
abstract class Sanitizer extends DataFlow::Node { }
9+
import Xss::DomBasedXss
3310

3411
/**
3512
* A taint-tracking configuration for reasoning about XSS.
@@ -68,146 +45,4 @@ module DomBasedXss {
6845
class LocationSource extends Source, DataFlow::ValueNode {
6946
LocationSource() { isDocumentURL(astNode) }
7047
}
71-
72-
/**
73-
* An expression whose value is interpreted as HTML
74-
* and may be inserted into the DOM through a library.
75-
*/
76-
class LibrarySink extends Sink, DataFlow::ValueNode {
77-
LibrarySink() {
78-
// call to a jQuery method that interprets its argument as HTML
79-
exists(JQueryMethodCall call | call.interpretsArgumentAsHtml(astNode) |
80-
// either the argument is always interpreted as HTML
81-
not call.interpretsArgumentAsSelector(astNode)
82-
or
83-
// or it doesn't start with something other than `<`, and so at least
84-
// _may_ be interpreted as HTML
85-
not exists(DataFlow::Node prefix, string strval |
86-
isPrefixOfJQueryHtmlString(astNode, prefix) and
87-
strval = prefix.asExpr().getStringValue() and
88-
not strval.regexpMatch("\\s*<.*")
89-
) and
90-
not isDocumentURL(astNode)
91-
)
92-
or
93-
// call to an Angular method that interprets its argument as HTML
94-
any(AngularJS::AngularJSCall call).interpretsArgumentAsHtml(this.asExpr())
95-
or
96-
// call to a WinJS function that interprets its argument as HTML
97-
exists(DataFlow::MethodCallNode mcn, string m |
98-
m = "setInnerHTMLUnsafe" or m = "setOuterHTMLUnsafe"
99-
|
100-
mcn.getMethodName() = m and
101-
this = mcn.getArgument(1)
102-
)
103-
}
104-
}
105-
106-
/**
107-
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
108-
* HTML by a jQuery method.
109-
*/
110-
private predicate isPrefixOfJQueryHtmlString(Expr htmlString, DataFlow::Node prefix) {
111-
any(JQueryMethodCall call).interpretsArgumentAsHtml(htmlString) and
112-
prefix = htmlString.flow()
113-
or
114-
exists(DataFlow::Node pred | isPrefixOfJQueryHtmlString(htmlString, pred) |
115-
prefix = StringConcatenation::getFirstOperand(pred)
116-
or
117-
prefix = pred.getAPredecessor()
118-
)
119-
}
120-
121-
/**
122-
* An expression whose value is interpreted as HTML or CSS
123-
* and may be inserted into the DOM.
124-
*/
125-
class DomSink extends Sink {
126-
DomSink() {
127-
// Call to a DOM function that inserts its argument into the DOM
128-
any(DomMethodCallExpr call).interpretsArgumentsAsHTML(this.asExpr())
129-
or
130-
// Assignment to a dangerous DOM property
131-
exists(DomPropWriteNode pw |
132-
pw.interpretsValueAsHTML() and
133-
this = DataFlow::valueNode(pw.getRhs())
134-
)
135-
or
136-
// `html` or `source.html` properties of React Native `WebView`
137-
exists(ReactNative::WebViewElement webView, DataFlow::SourceNode source |
138-
source = webView or
139-
source = webView.getAPropertyWrite("source").getRhs().getALocalSource()
140-
|
141-
this = source.getAPropertyWrite("html").getRhs()
142-
)
143-
}
144-
}
145-
146-
/**
147-
* An expression whose value is interpreted as HTML by a DOMParser.
148-
*/
149-
class DomParserSink extends Sink {
150-
DomParserSink() {
151-
exists(DataFlow::GlobalVarRefNode domParser |
152-
domParser.getName() = "DOMParser" and
153-
this = domParser.getAnInstantiation().getAMethodCall("parseFromString").getArgument(0)
154-
)
155-
}
156-
}
157-
158-
/**
159-
* A React `dangerouslySetInnerHTML` attribute, viewed as an XSS sink.
160-
*
161-
* Any write to the `__html` property of an object assigned to this attribute
162-
* is considered an XSS sink.
163-
*/
164-
class DangerouslySetInnerHtmlSink extends Sink, DataFlow::ValueNode {
165-
DangerouslySetInnerHtmlSink() {
166-
exists(DataFlow::Node danger, DataFlow::SourceNode valueSrc |
167-
exists(JSXAttribute attr |
168-
attr.getName() = "dangerouslySetInnerHTML" and
169-
attr.getValue() = danger.asExpr()
170-
)
171-
or
172-
exists(ReactElementDefinition def, DataFlow::ObjectLiteralNode props |
173-
props.flowsTo(def.getProps()) and
174-
props.hasPropertyWrite("dangerouslySetInnerHTML", danger)
175-
)
176-
|
177-
valueSrc.flowsTo(danger) and
178-
valueSrc.hasPropertyWrite("__html", this)
179-
)
180-
}
181-
}
182-
183-
/**
184-
* The HTML body of an email, viewed as an XSS sink.
185-
*/
186-
class EmailHtmlBodySink extends Sink {
187-
EmailHtmlBodySink() { this = any(EmailSender sender).getHtmlBody() }
188-
189-
override string getVulnerabilityKind() { result = "HTML injection" }
190-
}
191-
192-
193-
/**
194-
* A write to the `template` option of a Vue instance, viewed as an XSS sink.
195-
*/
196-
class VueTemplateSink extends DomBasedXss::Sink {
197-
VueTemplateSink() { this = any(Vue::Instance i).getTemplate() }
198-
}
199-
200-
/**
201-
* The tag name argument to the `createElement` parameter of the
202-
* `render` method of a Vue instance, viewed as an XSS sink.
203-
*/
204-
class VueCreateElementSink extends DomBasedXss::Sink {
205-
VueCreateElementSink() {
206-
exists(Vue::Instance i, DataFlow::FunctionNode f |
207-
f.flowsTo(i.getRender()) and
208-
this = f.getParameter(0).getACall().getArgument(0)
209-
)
210-
}
211-
}
212-
21348
}

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

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,9 @@
44
*/
55

66
import javascript
7-
import semmle.javascript.security.dataflow.RemoteFlowSources
8-
import semmle.javascript.frameworks.jQuery
97

108
module ReflectedXss {
11-
/**
12-
* A data flow source for XSS vulnerabilities.
13-
*/
14-
abstract class Source extends DataFlow::Node { }
15-
16-
/**
17-
* A data flow sink for XSS vulnerabilities.
18-
*/
19-
abstract class Sink extends DataFlow::Node { }
20-
21-
/**
22-
* A sanitizer for XSS vulnerabilities.
23-
*/
24-
abstract class Sanitizer extends DataFlow::Node { }
9+
import Xss::ReflectedXss
2510

2611
/**
2712
* A taint-tracking configuration for reasoning about XSS.
@@ -47,23 +32,4 @@ module ReflectedXss {
4732
this.(HTTP::RequestHeaderAccess).getAHeaderName() = "referer"
4833
}
4934
}
50-
51-
/**
52-
* An expression that is sent as part of an HTTP response, considered as an XSS sink.
53-
*
54-
* We exclude cases where the route handler sets either an unknown content type or
55-
* a content type that does not (case-insensitively) contain the string "html". This
56-
* is to prevent us from flagging plain-text or JSON responses as vulnerable.
57-
*/
58-
private class HttpResponseSink extends Sink {
59-
HttpResponseSink() {
60-
exists(HTTP::ResponseSendArgument sendarg | sendarg = asExpr() |
61-
forall(HTTP::HeaderDefinition hd |
62-
hd = sendarg.getRouteHandler().getAResponseHeader("content-type")
63-
|
64-
exists(string tp | hd.defines("content-type", tp) | tp.toLowerCase().matches("%html%"))
65-
)
66-
)
67-
}
68-
}
6935
}

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

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,9 @@
44
*/
55

66
import javascript
7-
import semmle.javascript.security.dataflow.RemoteFlowSources
8-
import semmle.javascript.security.dataflow.ReflectedXss as ReflectedXss
9-
import semmle.javascript.security.dataflow.DomBasedXss as DomBasedXss
107

118
module StoredXss {
12-
/**
13-
* A data flow source for XSS vulnerabilities.
14-
*/
15-
abstract class Source extends DataFlow::Node { }
16-
17-
/**
18-
* A data flow sink for XSS vulnerabilities.
19-
*/
20-
abstract class Sink extends DataFlow::Node { }
21-
22-
/**
23-
* A sanitizer for XSS vulnerabilities.
24-
*/
25-
abstract class Sanitizer extends DataFlow::Node { }
9+
import Xss::StoredXss
2610

2711
/**
2812
* A taint-tracking configuration for reasoning about XSS.
@@ -44,12 +28,4 @@ module StoredXss {
4428
class FileNameSourceAsSource extends Source {
4529
FileNameSourceAsSource() { this instanceof FileNameSource }
4630
}
47-
48-
/** An ordinary XSS sink, considered as a flow sink for stored XSS. */
49-
class XssSinkAsSink extends Sink {
50-
XssSinkAsSink() {
51-
this instanceof ReflectedXss::ReflectedXss::Sink or
52-
this instanceof DomBasedXss::DomBasedXss::Sink
53-
}
54-
}
5531
}

0 commit comments

Comments
 (0)