Merge pull request #910 from xiemaisi/js/regexp-taint

Approved by esben-semmle
This commit is contained in:
semmle-qlci
2019-02-12 13:15:16 +00:00
committed by GitHub
9 changed files with 315 additions and 622 deletions

View File

@@ -473,6 +473,27 @@ module TaintTracking {
}
}
/**
* A taint-propagating data flow edge from the first (and only) argument in a call to
* `RegExp.prototype.exec` to its result.
*/
private class RegExpExecTaintStep extends AdditionalTaintStep {
DataFlow::MethodCallNode self;
RegExpExecTaintStep() {
this = self and
self.getReceiver().analyze().getAType() = TTRegExp() and
self.getMethodName() = "exec" and
self.getNumArgument() = 1
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = self.getArgument(0) and
succ = this
}
}
/**
* A taint propagating data flow edge arising from JSON unparsing.
*/

View File

@@ -4,32 +4,9 @@
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
import semmle.javascript.frameworks.jQuery
module DomBasedXss {
/**
* A data flow source for XSS vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for XSS vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/**
* Gets the kind of vulnerability to report in the alert message.
*
* Defaults to `Cross-site scripting`, but may be overriden for sinks
* that do not allow script injection, but injection of other undesirable HTML elements.
*/
string getVulnerabilityKind() { result = "Cross-site scripting" }
}
/**
* A sanitizer for XSS vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
import Xss::DomBasedXss
/**
* A taint-tracking configuration for reasoning about XSS.
@@ -68,146 +45,4 @@ module DomBasedXss {
class LocationSource extends Source, DataFlow::ValueNode {
LocationSource() { isDocumentURL(astNode) }
}
/**
* An expression whose value is interpreted as HTML
* and may be inserted into the DOM through a library.
*/
class LibrarySink extends Sink, DataFlow::ValueNode {
LibrarySink() {
// call to a jQuery method that interprets its argument as HTML
exists(JQueryMethodCall call | call.interpretsArgumentAsHtml(astNode) |
// either the argument is always interpreted as HTML
not call.interpretsArgumentAsSelector(astNode)
or
// or it doesn't start with something other than `<`, and so at least
// _may_ be interpreted as HTML
not exists(DataFlow::Node prefix, string strval |
isPrefixOfJQueryHtmlString(astNode, prefix) and
strval = prefix.asExpr().getStringValue() and
not strval.regexpMatch("\\s*<.*")
) and
not isDocumentURL(astNode)
)
or
// call to an Angular method that interprets its argument as HTML
any(AngularJS::AngularJSCall call).interpretsArgumentAsHtml(this.asExpr())
or
// call to a WinJS function that interprets its argument as HTML
exists(DataFlow::MethodCallNode mcn, string m |
m = "setInnerHTMLUnsafe" or m = "setOuterHTMLUnsafe"
|
mcn.getMethodName() = m and
this = mcn.getArgument(1)
)
}
}
/**
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
* HTML by a jQuery method.
*/
private predicate isPrefixOfJQueryHtmlString(Expr htmlString, DataFlow::Node prefix) {
any(JQueryMethodCall call).interpretsArgumentAsHtml(htmlString) and
prefix = htmlString.flow()
or
exists(DataFlow::Node pred | isPrefixOfJQueryHtmlString(htmlString, pred) |
prefix = StringConcatenation::getFirstOperand(pred)
or
prefix = pred.getAPredecessor()
)
}
/**
* An expression whose value is interpreted as HTML or CSS
* and may be inserted into the DOM.
*/
class DomSink extends Sink {
DomSink() {
// Call to a DOM function that inserts its argument into the DOM
any(DomMethodCallExpr call).interpretsArgumentsAsHTML(this.asExpr())
or
// Assignment to a dangerous DOM property
exists(DomPropWriteNode pw |
pw.interpretsValueAsHTML() and
this = DataFlow::valueNode(pw.getRhs())
)
or
// `html` or `source.html` properties of React Native `WebView`
exists(ReactNative::WebViewElement webView, DataFlow::SourceNode source |
source = webView or
source = webView.getAPropertyWrite("source").getRhs().getALocalSource()
|
this = source.getAPropertyWrite("html").getRhs()
)
}
}
/**
* An expression whose value is interpreted as HTML by a DOMParser.
*/
class DomParserSink extends Sink {
DomParserSink() {
exists(DataFlow::GlobalVarRefNode domParser |
domParser.getName() = "DOMParser" and
this = domParser.getAnInstantiation().getAMethodCall("parseFromString").getArgument(0)
)
}
}
/**
* A React `dangerouslySetInnerHTML` attribute, viewed as an XSS sink.
*
* Any write to the `__html` property of an object assigned to this attribute
* is considered an XSS sink.
*/
class DangerouslySetInnerHtmlSink extends Sink, DataFlow::ValueNode {
DangerouslySetInnerHtmlSink() {
exists(DataFlow::Node danger, DataFlow::SourceNode valueSrc |
exists(JSXAttribute attr |
attr.getName() = "dangerouslySetInnerHTML" and
attr.getValue() = danger.asExpr()
)
or
exists(ReactElementDefinition def, DataFlow::ObjectLiteralNode props |
props.flowsTo(def.getProps()) and
props.hasPropertyWrite("dangerouslySetInnerHTML", danger)
)
|
valueSrc.flowsTo(danger) and
valueSrc.hasPropertyWrite("__html", this)
)
}
}
/**
* The HTML body of an email, viewed as an XSS sink.
*/
class EmailHtmlBodySink extends Sink {
EmailHtmlBodySink() { this = any(EmailSender sender).getHtmlBody() }
override string getVulnerabilityKind() { result = "HTML injection" }
}
/**
* A write to the `template` option of a Vue instance, viewed as an XSS sink.
*/
class VueTemplateSink extends DomBasedXss::Sink {
VueTemplateSink() { this = any(Vue::Instance i).getTemplate() }
}
/**
* The tag name argument to the `createElement` parameter of the
* `render` method of a Vue instance, viewed as an XSS sink.
*/
class VueCreateElementSink extends DomBasedXss::Sink {
VueCreateElementSink() {
exists(Vue::Instance i, DataFlow::FunctionNode f |
f.flowsTo(i.getRender()) and
this = f.getParameter(0).getACall().getArgument(0)
)
}
}
}

View File

@@ -4,24 +4,9 @@
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
import semmle.javascript.frameworks.jQuery
module ReflectedXss {
/**
* A data flow source for XSS vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for XSS vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for XSS vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
import Xss::ReflectedXss
/**
* A taint-tracking configuration for reasoning about XSS.
@@ -47,23 +32,4 @@ module ReflectedXss {
this.(HTTP::RequestHeaderAccess).getAHeaderName() = "referer"
}
}
/**
* An expression that is sent as part of an HTTP response, considered as an XSS sink.
*
* We exclude cases where the route handler sets either an unknown content type or
* a content type that does not (case-insensitively) contain the string "html". This
* is to prevent us from flagging plain-text or JSON responses as vulnerable.
*/
private class HttpResponseSink extends Sink {
HttpResponseSink() {
exists(HTTP::ResponseSendArgument sendarg | sendarg = asExpr() |
forall(HTTP::HeaderDefinition hd |
hd = sendarg.getRouteHandler().getAResponseHeader("content-type")
|
exists(string tp | hd.defines("content-type", tp) | tp.toLowerCase().matches("%html%"))
)
)
}
}
}

View File

@@ -4,25 +4,9 @@
*/
import javascript
import semmle.javascript.security.dataflow.RemoteFlowSources
import semmle.javascript.security.dataflow.ReflectedXss as ReflectedXss
import semmle.javascript.security.dataflow.DomBasedXss as DomBasedXss
module StoredXss {
/**
* A data flow source for XSS vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for XSS vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for XSS vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
import Xss::StoredXss
/**
* A taint-tracking configuration for reasoning about XSS.
@@ -44,12 +28,4 @@ module StoredXss {
class FileNameSourceAsSource extends Source {
FileNameSourceAsSource() { this instanceof FileNameSource }
}
/** An ordinary XSS sink, considered as a flow sink for stored XSS. */
class XssSinkAsSink extends Sink {
XssSinkAsSink() {
this instanceof ReflectedXss::ReflectedXss::Sink or
this instanceof DomBasedXss::DomBasedXss::Sink
}
}
}

View File

@@ -0,0 +1,267 @@
/**
* Provides classes and predicates used by the XSS queries.
*/
import javascript
/** Provides classes and predicates shared between the XSS queries. */
module Shared {
/** A data flow source for XSS vulnerabilities. */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for XSS vulnerabilities. */
abstract class Sink extends DataFlow::Node {
/**
* Gets the kind of vulnerability to report in the alert message.
*
* Defaults to `Cross-site scripting`, but may be overriden for sinks
* that do not allow script injection, but injection of other undesirable HTML elements.
*/
string getVulnerabilityKind() { result = "Cross-site scripting" }
}
/** A sanitizer for XSS vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/**
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
* XSS vulnerabilities.
*
* The XSS queries do not attempt to reason about correctness or completeness of sanitizers,
* so any such replacement stops taint propagation.
*/
class MetacharEscapeSanitizer extends Sanitizer, DataFlow::MethodCallNode {
MetacharEscapeSanitizer() {
getMethodName() = "replace" and
exists(RegExpConstant c |
c.getLiteral() = getArgument(0).asExpr() and
c.getValue().regexpMatch("['\"&<>]")
)
}
}
}
/** Provides classes and predicates for the DOM-based XSS query. */
module DomBasedXss {
/** A data flow source for DOM-based XSS vulnerabilities. */
abstract class Source extends Shared::Source { }
/** A data flow sink for DOM-based XSS vulnerabilities. */
abstract class Sink extends Shared::Sink { }
/** A sanitizer for DOM-based XSS vulnerabilities. */
abstract class Sanitizer extends Shared::Sanitizer { }
/**
* An expression whose value is interpreted as HTML
* and may be inserted into the DOM through a library.
*/
class LibrarySink extends Sink, DataFlow::ValueNode {
LibrarySink() {
// call to a jQuery method that interprets its argument as HTML
exists(JQueryMethodCall call | call.interpretsArgumentAsHtml(astNode) |
// either the argument is always interpreted as HTML
not call.interpretsArgumentAsSelector(astNode)
or
// or it doesn't start with something other than `<`, and so at least
// _may_ be interpreted as HTML
not exists(DataFlow::Node prefix, string strval |
isPrefixOfJQueryHtmlString(astNode, prefix) and
strval = prefix.asExpr().getStringValue() and
not strval.regexpMatch("\\s*<.*")
) and
not isDocumentURL(astNode)
)
or
// call to an Angular method that interprets its argument as HTML
any(AngularJS::AngularJSCall call).interpretsArgumentAsHtml(this.asExpr())
or
// call to a WinJS function that interprets its argument as HTML
exists(DataFlow::MethodCallNode mcn, string m |
m = "setInnerHTMLUnsafe" or m = "setOuterHTMLUnsafe"
|
mcn.getMethodName() = m and
this = mcn.getArgument(1)
)
}
}
/**
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
* HTML by a jQuery method.
*/
private predicate isPrefixOfJQueryHtmlString(Expr htmlString, DataFlow::Node prefix) {
any(JQueryMethodCall call).interpretsArgumentAsHtml(htmlString) and
prefix = htmlString.flow()
or
exists(DataFlow::Node pred | isPrefixOfJQueryHtmlString(htmlString, pred) |
prefix = StringConcatenation::getFirstOperand(pred)
or
prefix = pred.getAPredecessor()
)
}
/**
* An expression whose value is interpreted as HTML or CSS
* and may be inserted into the DOM.
*/
class DomSink extends Sink {
DomSink() {
// Call to a DOM function that inserts its argument into the DOM
any(DomMethodCallExpr call).interpretsArgumentsAsHTML(this.asExpr())
or
// Assignment to a dangerous DOM property
exists(DomPropWriteNode pw |
pw.interpretsValueAsHTML() and
this = DataFlow::valueNode(pw.getRhs())
)
or
// `html` or `source.html` properties of React Native `WebView`
exists(ReactNative::WebViewElement webView, DataFlow::SourceNode source |
source = webView or
source = webView.getAPropertyWrite("source").getRhs().getALocalSource()
|
this = source.getAPropertyWrite("html").getRhs()
)
}
}
/**
* An expression whose value is interpreted as HTML by a DOMParser.
*/
class DomParserSink extends Sink {
DomParserSink() {
exists(DataFlow::GlobalVarRefNode domParser |
domParser.getName() = "DOMParser" and
this = domParser.getAnInstantiation().getAMethodCall("parseFromString").getArgument(0)
)
}
}
/**
* A React `dangerouslySetInnerHTML` attribute, viewed as an XSS sink.
*
* Any write to the `__html` property of an object assigned to this attribute
* is considered an XSS sink.
*/
class DangerouslySetInnerHtmlSink extends Sink, DataFlow::ValueNode {
DangerouslySetInnerHtmlSink() {
exists(DataFlow::Node danger, DataFlow::SourceNode valueSrc |
exists(JSXAttribute attr |
attr.getName() = "dangerouslySetInnerHTML" and
attr.getValue() = danger.asExpr()
)
or
exists(ReactElementDefinition def, DataFlow::ObjectLiteralNode props |
props.flowsTo(def.getProps()) and
props.hasPropertyWrite("dangerouslySetInnerHTML", danger)
)
|
valueSrc.flowsTo(danger) and
valueSrc.hasPropertyWrite("__html", this)
)
}
}
/**
* The HTML body of an email, viewed as an XSS sink.
*/
class EmailHtmlBodySink extends Sink {
EmailHtmlBodySink() { this = any(EmailSender sender).getHtmlBody() }
override string getVulnerabilityKind() { result = "HTML injection" }
}
/**
* A write to the `template` option of a Vue instance, viewed as an XSS sink.
*/
class VueTemplateSink extends DomBasedXss::Sink {
VueTemplateSink() { this = any(Vue::Instance i).getTemplate() }
}
/**
* The tag name argument to the `createElement` parameter of the
* `render` method of a Vue instance, viewed as an XSS sink.
*/
class VueCreateElementSink extends DomBasedXss::Sink {
VueCreateElementSink() {
exists(Vue::Instance i, DataFlow::FunctionNode f |
f.flowsTo(i.getRender()) and
this = f.getParameter(0).getACall().getArgument(0)
)
}
}
/**
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
* XSS vulnerabilities.
*
* The XSS queries do not attempt to reason about correctness or completeness of sanitizers,
* so any such replacement stops taint propagation.
*/
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
}
/** Provides classes and predicates for the reflected XSS query. */
module ReflectedXss {
/** A data flow source for reflected XSS vulnerabilities. */
abstract class Source extends Shared::Source { }
/** A data flow sink for reflected XSS vulnerabilities. */
abstract class Sink extends Shared::Sink { }
/** A sanitizer for reflected XSS vulnerabilities. */
abstract class Sanitizer extends Shared::Sanitizer { }
/**
* An expression that is sent as part of an HTTP response, considered as an XSS sink.
*
* We exclude cases where the route handler sets either an unknown content type or
* a content type that does not (case-insensitively) contain the string "html". This
* is to prevent us from flagging plain-text or JSON responses as vulnerable.
*/
private class HttpResponseSink extends Sink {
HttpResponseSink() {
exists(HTTP::ResponseSendArgument sendarg | sendarg = asExpr() |
forall(HTTP::HeaderDefinition hd |
hd = sendarg.getRouteHandler().getAResponseHeader("content-type")
|
exists(string tp | hd.defines("content-type", tp) | tp.toLowerCase().matches("%html%"))
)
)
}
}
/**
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
* XSS vulnerabilities.
*
* The XSS queries do not attempt to reason about correctness or completeness of sanitizers,
* so any such replacement stops taint propagation.
*/
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
}
/** Provides classes and predicates for the stored XSS query. */
module StoredXss {
/** A data flow source for stored XSS vulnerabilities. */
abstract class Source extends Shared::Source { }
/** A data flow sink for stored XSS vulnerabilities. */
abstract class Sink extends Shared::Sink { }
/** A sanitizer for stored XSS vulnerabilities. */
abstract class Sanitizer extends Shared::Sanitizer { }
/** An arbitrary XSS sink, considered as a flow sink for stored XSS. */
private class AnySink extends Sink { AnySink() { this instanceof Shared::Sink } }
/**
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
* XSS vulnerabilities.
*
* The XSS queries do not attempt to reason about correctness or completeness of sanitizers,
* so any such replacement stops taint propagation.
*/
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
}