mirror of
https://github.com/github/codeql.git
synced 2026-05-05 13:45:19 +02:00
move the DomBasedXss sources/sinks into the Customizations file
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.security.dataflow.Xss
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.security.dataflow.Xss as Xss
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
|
||||
module Cheerio {
|
||||
/** Gets a reference to the `cheerio` function, possibly with a loaded DOM. */
|
||||
@@ -95,7 +95,7 @@ module Cheerio {
|
||||
/**
|
||||
* An XSS sink through `cheerio`.
|
||||
*/
|
||||
class XssSink extends Xss::DomBasedXss::Sink {
|
||||
class XssSink extends DomBasedXss::Sink {
|
||||
XssSink() {
|
||||
exists(string name | this = cheerioObjectRef().getAMethodCall(name).getAnArgument() |
|
||||
JQuery::isMethodArgumentInterpretedAsHtml(name)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Provides classes and predicates modeling aspects of the `d3` library. */
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.security.dataflow.Xss
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
|
||||
/** Provides classes and predicates modeling aspects of the `d3` library. */
|
||||
module D3 {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.security.dataflow.Xss
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
private import HeuristicSinks as Sinks
|
||||
|
||||
class HeuristicSink = Sinks::HeuristicSink;
|
||||
@@ -30,7 +31,7 @@ private class HeuristicCommandInjectionSink extends HeuristicSink, CommandInject
|
||||
}
|
||||
}
|
||||
|
||||
private class HeuristicDomBasedXssSink extends HeuristicSink, Xss::DomBasedXss::Sink {
|
||||
private class HeuristicDomBasedXssSink extends HeuristicSink, DomBasedXss::Sink {
|
||||
HeuristicDomBasedXssSink() {
|
||||
isAssignedToOrConcatenatedWith(this, "(?i)(html|innerhtml)") or
|
||||
isArgTo(this, "(?i)(html|render)") or
|
||||
|
||||
@@ -4,9 +4,322 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
|
||||
module DomBasedXss {
|
||||
import Xss::DomBasedXss
|
||||
private import Xss::Shared as Shared
|
||||
|
||||
/** 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 { }
|
||||
|
||||
/** A sanitizer guard for DOM-based XSS vulnerabilities. */
|
||||
abstract class SanitizerGuard extends Shared::SanitizerGuard { }
|
||||
|
||||
/**
|
||||
* An expression whose value is interpreted as HTML
|
||||
* and may be inserted into the DOM through a library.
|
||||
*/
|
||||
class LibrarySink extends Sink {
|
||||
LibrarySink() {
|
||||
// call to a jQuery method that interprets its argument as HTML
|
||||
exists(JQuery::MethodCall call |
|
||||
call.interpretsArgumentAsHtml(this) and
|
||||
not call.interpretsArgumentAsSelector(this) // Handled by `JQuerySelectorSink`
|
||||
)
|
||||
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)
|
||||
)
|
||||
or
|
||||
this = any(Typeahead::TypeaheadSuggestionFunction f).getAReturn()
|
||||
or
|
||||
this = any(Handlebars::SafeString s).getAnArgument()
|
||||
or
|
||||
this = any(JQuery::MethodCall call | call.getMethodName() = "jGrowl").getArgument(0)
|
||||
or
|
||||
// A construction of a JSDOM object (server side DOM), where scripts are allowed.
|
||||
exists(DataFlow::NewNode instance |
|
||||
instance = API::moduleImport("jsdom").getMember("JSDOM").getInstance().getAnImmediateUse() and
|
||||
this = instance.getArgument(0) and
|
||||
instance.getOptionArgument(1, "runScripts").mayHaveStringValue("dangerously")
|
||||
)
|
||||
or
|
||||
MooTools::interpretsNodeAsHtml(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
|
||||
* HTML by a jQuery method.
|
||||
*/
|
||||
predicate isPrefixOfJQueryHtmlString(DataFlow::Node htmlString, DataFlow::Node prefix) {
|
||||
prefix = getAPrefixOfJQuerySelectorString(htmlString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
|
||||
* HTML by a jQuery method.
|
||||
*/
|
||||
private DataFlow::Node getAPrefixOfJQuerySelectorString(DataFlow::Node htmlString) {
|
||||
any(JQuery::MethodCall call).interpretsArgumentAsSelector(htmlString) and
|
||||
result = htmlString
|
||||
or
|
||||
exists(DataFlow::Node pred | pred = getAPrefixOfJQuerySelectorString(htmlString) |
|
||||
result = StringConcatenation::getFirstOperand(pred)
|
||||
or
|
||||
result = pred.getAPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to the jQuery `$` function or similar, which is interpreted as either a selector
|
||||
* or as an HTML string depending on its first character.
|
||||
*/
|
||||
class JQueryHtmlOrSelectorArgument extends DataFlow::Node {
|
||||
JQueryHtmlOrSelectorArgument() {
|
||||
exists(JQuery::MethodCall call |
|
||||
call.interpretsArgumentAsHtml(this) and
|
||||
call.interpretsArgumentAsSelector(this) and
|
||||
pragma[only_bind_out](this.analyze()).getAType() = TTString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a string that flows to the prefix of this argument. */
|
||||
string getAPrefix() { result = getAPrefixOfJQuerySelectorString(this).getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to the jQuery `$` function or similar, which may be interpreted as HTML.
|
||||
*
|
||||
* This is the same as `JQueryHtmlOrSelectorArgument`, excluding cases where the value
|
||||
* is prefixed by something other than `<`.
|
||||
*/
|
||||
class JQueryHtmlOrSelectorSink extends Sink, JQueryHtmlOrSelectorArgument {
|
||||
JQueryHtmlOrSelectorSink() {
|
||||
// If a prefix of the string is known, it must start with '<' or be an empty string
|
||||
forall(string strval | strval = this.getAPrefix() | strval.regexpMatch("(?s)\\s*<.*|"))
|
||||
}
|
||||
}
|
||||
|
||||
import ClientSideUrlRedirectCustomizations::ClientSideUrlRedirect as ClientSideUrlRedirect
|
||||
|
||||
/**
|
||||
* A write to a URL which may execute JavaScript code.
|
||||
*/
|
||||
class WriteURLSink extends Sink instanceof ClientSideUrlRedirect::Sink {
|
||||
WriteURLSink() { super.isXssSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class HtmlParserSink extends Sink {
|
||||
HtmlParserSink() {
|
||||
exists(DataFlow::GlobalVarRefNode domParser |
|
||||
domParser.getName() = "DOMParser" and
|
||||
this = domParser.getAnInstantiation().getAMethodCall("parseFromString").getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode ccf |
|
||||
isDomValue(ccf.getReceiver().asExpr()) and
|
||||
ccf.getMethodName() = "createContextualFragment" and
|
||||
this = ccf.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A React tooltip where the `data-html` attribute is set to `true`.
|
||||
*/
|
||||
class TooltipSink extends Sink {
|
||||
TooltipSink() {
|
||||
exists(JsxElement el |
|
||||
el.getAttributeByName("data-html").getStringValue() = "true" or
|
||||
el.getAttributeByName("data-html").getValue().mayHaveBooleanValue(true)
|
||||
|
|
||||
this = el.getAttributeByName("data-tip").getValue().flow()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Sink {
|
||||
VueTemplateSink() {
|
||||
// Note: don't use Vue::Component#getTemplate as it includes an unwanted getALocalSource() step
|
||||
this = any(Vue::Component c).getOption("template")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag name argument to the `createElement` parameter of the
|
||||
* `render` method of a Vue instance, viewed as an XSS sink.
|
||||
*/
|
||||
class VueCreateElementSink extends Sink {
|
||||
VueCreateElementSink() {
|
||||
exists(Vue::Component c, DataFlow::FunctionNode f |
|
||||
f.flowsTo(c.getRender()) and
|
||||
this = f.getParameter(0).getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue `v-html` attribute, viewed as an XSS sink.
|
||||
*/
|
||||
class VHtmlSink extends Vue::VHtmlAttribute, Sink { }
|
||||
|
||||
/**
|
||||
* A raw interpolation tag in a template file, viewed as an XSS sink.
|
||||
*/
|
||||
class TemplateSink extends Sink {
|
||||
TemplateSink() {
|
||||
exists(Templating::TemplatePlaceholderTag tag |
|
||||
tag.isRawInterpolation() and
|
||||
this = tag.asDataFlowNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value being piped into the `safe` pipe in a template file,
|
||||
* disabling subsequent HTML escaping.
|
||||
*/
|
||||
class SafePipe extends Sink {
|
||||
SafePipe() { this = Templating::getAPipeCall("safe").getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A property read from a safe property is considered a sanitizer.
|
||||
*/
|
||||
class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node {
|
||||
SafePropertyReadSanitizer() {
|
||||
exists(PropAccess pacc | pacc = this.asExpr() | pacc.getPropertyName() = "length")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 { }
|
||||
|
||||
private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { }
|
||||
|
||||
private class SerializeJavascriptSanitizer extends Sanitizer, Shared::SerializeJavascriptSanitizer {
|
||||
}
|
||||
|
||||
private class IsEscapedInSwitchSanitizer extends Sanitizer, Shared::IsEscapedInSwitchSanitizer { }
|
||||
|
||||
private class QuoteGuard extends SanitizerGuard, Shared::QuoteGuard { }
|
||||
|
||||
/**
|
||||
* Holds if there exists two dataflow edges to `succ`, where one edges is sanitized, and the other edge starts with `pred`.
|
||||
*/
|
||||
predicate isOptionallySanitizedEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(HtmlSanitizerCall sanitizer |
|
||||
// sanitized = sanitize ? sanitizer(source) : source;
|
||||
exists(ConditionalExpr branch, Variable var, VarAccess access |
|
||||
branch = succ.asExpr() and access = var.getAnAccess()
|
||||
|
|
||||
branch.getABranch() = access and
|
||||
pred.getEnclosingExpr() = access and
|
||||
sanitizer = branch.getABranch().flow() and
|
||||
sanitizer.getAnArgument().getEnclosingExpr() = var.getAnAccess()
|
||||
)
|
||||
or
|
||||
// sanitized = source; if (sanitize) {sanitized = sanitizer(source)};
|
||||
exists(SsaPhiNode phi, SsaExplicitDefinition a, SsaDefinition b |
|
||||
a = phi.getAnInput().getDefinition() and
|
||||
b = phi.getAnInput().getDefinition() and
|
||||
count(phi.getAnInput()) = 2 and
|
||||
not a = b and
|
||||
sanitizer = DataFlow::valueNode(a.getDef().getSource()) and
|
||||
sanitizer.getAnArgument().asExpr().(VarAccess).getVariable() = b.getSourceVariable()
|
||||
|
|
||||
pred = DataFlow::ssaDefinitionNode(b) and
|
||||
succ = DataFlow::ssaDefinitionNode(phi)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private class ContainsHtmlGuard extends SanitizerGuard, Shared::ContainsHtmlGuard { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
|
||||
class RemoteFlowSourceAsSource extends Source {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
import semmle.javascript.security.dataflow.Xss
|
||||
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
|
||||
module UnsafeJQueryPlugin {
|
||||
private import DataFlow::FlowLabel
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.Xss
|
||||
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
import UnsafeJQueryPluginCustomizations::UnsafeJQueryPlugin
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
private import semmle.javascript.dataflow.InferredTypes // TODO: Try to remove.
|
||||
|
||||
/** Provides classes and predicates shared between the XSS queries. */
|
||||
module Shared {
|
||||
@@ -146,319 +146,12 @@ module Shared {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 { }
|
||||
|
||||
/** A sanitizer guard for DOM-based XSS vulnerabilities. */
|
||||
abstract class SanitizerGuard extends Shared::SanitizerGuard { }
|
||||
|
||||
/**
|
||||
* An expression whose value is interpreted as HTML
|
||||
* and may be inserted into the DOM through a library.
|
||||
*/
|
||||
class LibrarySink extends Sink {
|
||||
LibrarySink() {
|
||||
// call to a jQuery method that interprets its argument as HTML
|
||||
exists(JQuery::MethodCall call |
|
||||
call.interpretsArgumentAsHtml(this) and
|
||||
not call.interpretsArgumentAsSelector(this) // Handled by `JQuerySelectorSink`
|
||||
)
|
||||
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)
|
||||
)
|
||||
or
|
||||
this = any(Typeahead::TypeaheadSuggestionFunction f).getAReturn()
|
||||
or
|
||||
this = any(Handlebars::SafeString s).getAnArgument()
|
||||
or
|
||||
this = any(JQuery::MethodCall call | call.getMethodName() = "jGrowl").getArgument(0)
|
||||
or
|
||||
// A construction of a JSDOM object (server side DOM), where scripts are allowed.
|
||||
exists(DataFlow::NewNode instance |
|
||||
instance = API::moduleImport("jsdom").getMember("JSDOM").getInstance().getAnImmediateUse() and
|
||||
this = instance.getArgument(0) and
|
||||
instance.getOptionArgument(1, "runScripts").mayHaveStringValue("dangerously")
|
||||
)
|
||||
or
|
||||
MooTools::interpretsNodeAsHtml(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
|
||||
* HTML by a jQuery method.
|
||||
*/
|
||||
predicate isPrefixOfJQueryHtmlString(DataFlow::Node htmlString, DataFlow::Node prefix) {
|
||||
prefix = getAPrefixOfJQuerySelectorString(htmlString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as
|
||||
* HTML by a jQuery method.
|
||||
*/
|
||||
private DataFlow::Node getAPrefixOfJQuerySelectorString(DataFlow::Node htmlString) {
|
||||
any(JQuery::MethodCall call).interpretsArgumentAsSelector(htmlString) and
|
||||
result = htmlString
|
||||
or
|
||||
exists(DataFlow::Node pred | pred = getAPrefixOfJQuerySelectorString(htmlString) |
|
||||
result = StringConcatenation::getFirstOperand(pred)
|
||||
or
|
||||
result = pred.getAPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to the jQuery `$` function or similar, which is interpreted as either a selector
|
||||
* or as an HTML string depending on its first character.
|
||||
*/
|
||||
class JQueryHtmlOrSelectorArgument extends DataFlow::Node {
|
||||
JQueryHtmlOrSelectorArgument() {
|
||||
exists(JQuery::MethodCall call |
|
||||
call.interpretsArgumentAsHtml(this) and
|
||||
call.interpretsArgumentAsSelector(this) and
|
||||
pragma[only_bind_out](this.analyze()).getAType() = TTString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a string that flows to the prefix of this argument. */
|
||||
string getAPrefix() { result = getAPrefixOfJQuerySelectorString(this).getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to the jQuery `$` function or similar, which may be interpreted as HTML.
|
||||
*
|
||||
* This is the same as `JQueryHtmlOrSelectorArgument`, excluding cases where the value
|
||||
* is prefixed by something other than `<`.
|
||||
*/
|
||||
class JQueryHtmlOrSelectorSink extends Sink, JQueryHtmlOrSelectorArgument {
|
||||
JQueryHtmlOrSelectorSink() {
|
||||
// If a prefix of the string is known, it must start with '<' or be an empty string
|
||||
forall(string strval | strval = this.getAPrefix() | strval.regexpMatch("(?s)\\s*<.*|"))
|
||||
}
|
||||
}
|
||||
|
||||
import ClientSideUrlRedirectCustomizations::ClientSideUrlRedirect as ClientSideUrlRedirect
|
||||
|
||||
/**
|
||||
* A write to a URL which may execute JavaScript code.
|
||||
*/
|
||||
class WriteURLSink extends Sink instanceof ClientSideUrlRedirect::Sink {
|
||||
WriteURLSink() { super.isXssSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class HtmlParserSink extends Sink {
|
||||
HtmlParserSink() {
|
||||
exists(DataFlow::GlobalVarRefNode domParser |
|
||||
domParser.getName() = "DOMParser" and
|
||||
this = domParser.getAnInstantiation().getAMethodCall("parseFromString").getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode ccf |
|
||||
isDomValue(ccf.getReceiver().asExpr()) and
|
||||
ccf.getMethodName() = "createContextualFragment" and
|
||||
this = ccf.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A React tooltip where the `data-html` attribute is set to `true`.
|
||||
*/
|
||||
class TooltipSink extends Sink {
|
||||
TooltipSink() {
|
||||
exists(JsxElement el |
|
||||
el.getAttributeByName("data-html").getStringValue() = "true" or
|
||||
el.getAttributeByName("data-html").getValue().mayHaveBooleanValue(true)
|
||||
|
|
||||
this = el.getAttributeByName("data-tip").getValue().flow()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Sink {
|
||||
VueTemplateSink() {
|
||||
// Note: don't use Vue::Component#getTemplate as it includes an unwanted getALocalSource() step
|
||||
this = any(Vue::Component c).getOption("template")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag name argument to the `createElement` parameter of the
|
||||
* `render` method of a Vue instance, viewed as an XSS sink.
|
||||
*/
|
||||
class VueCreateElementSink extends Sink {
|
||||
VueCreateElementSink() {
|
||||
exists(Vue::Component c, DataFlow::FunctionNode f |
|
||||
f.flowsTo(c.getRender()) and
|
||||
this = f.getParameter(0).getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue `v-html` attribute, viewed as an XSS sink.
|
||||
*/
|
||||
class VHtmlSink extends Vue::VHtmlAttribute, Sink { }
|
||||
|
||||
/**
|
||||
* A raw interpolation tag in a template file, viewed as an XSS sink.
|
||||
*/
|
||||
class TemplateSink extends Sink {
|
||||
TemplateSink() {
|
||||
exists(Templating::TemplatePlaceholderTag tag |
|
||||
tag.isRawInterpolation() and
|
||||
this = tag.asDataFlowNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value being piped into the `safe` pipe in a template file,
|
||||
* disabling subsequent HTML escaping.
|
||||
*/
|
||||
class SafePipe extends Sink {
|
||||
SafePipe() { this = Templating::getAPipeCall("safe").getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A property read from a safe property is considered a sanitizer.
|
||||
*/
|
||||
class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node {
|
||||
SafePropertyReadSanitizer() {
|
||||
exists(PropAccess pacc | pacc = this.asExpr() | pacc.getPropertyName() = "length")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 { }
|
||||
|
||||
private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { }
|
||||
|
||||
private class SerializeJavascriptSanitizer extends Sanitizer, Shared::SerializeJavascriptSanitizer {
|
||||
}
|
||||
|
||||
private class IsEscapedInSwitchSanitizer extends Sanitizer, Shared::IsEscapedInSwitchSanitizer { }
|
||||
|
||||
private class QuoteGuard extends SanitizerGuard, Shared::QuoteGuard { }
|
||||
|
||||
/**
|
||||
* Holds if there exists two dataflow edges to `succ`, where one edges is sanitized, and the other edge starts with `pred`.
|
||||
*/
|
||||
predicate isOptionallySanitizedEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(HtmlSanitizerCall sanitizer |
|
||||
// sanitized = sanitize ? sanitizer(source) : source;
|
||||
exists(ConditionalExpr branch, Variable var, VarAccess access |
|
||||
branch = succ.asExpr() and access = var.getAnAccess()
|
||||
|
|
||||
branch.getABranch() = access and
|
||||
pred.getEnclosingExpr() = access and
|
||||
sanitizer = branch.getABranch().flow() and
|
||||
sanitizer.getAnArgument().getEnclosingExpr() = var.getAnAccess()
|
||||
)
|
||||
or
|
||||
// sanitized = source; if (sanitize) {sanitized = sanitizer(source)};
|
||||
exists(SsaPhiNode phi, SsaExplicitDefinition a, SsaDefinition b |
|
||||
a = phi.getAnInput().getDefinition() and
|
||||
b = phi.getAnInput().getDefinition() and
|
||||
count(phi.getAnInput()) = 2 and
|
||||
not a = b and
|
||||
sanitizer = DataFlow::valueNode(a.getDef().getSource()) and
|
||||
sanitizer.getAnArgument().asExpr().(VarAccess).getVariable() = b.getSourceVariable()
|
||||
|
|
||||
pred = DataFlow::ssaDefinitionNode(b) and
|
||||
succ = DataFlow::ssaDefinitionNode(phi)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private class ContainsHtmlGuard extends SanitizerGuard, Shared::ContainsHtmlGuard { }
|
||||
/**
|
||||
* DEPRECATED: Use the `DomBasedXssCustomizations.qll` file instead.
|
||||
* Provides classes and predicates for the DOM-based XSS query.
|
||||
*/
|
||||
deprecated module DomBasedXss {
|
||||
import DomBasedXssCustomizations::DomBasedXss
|
||||
}
|
||||
|
||||
/** Provides classes and predicates for the reflected XSS query. */
|
||||
|
||||
@@ -11,7 +11,7 @@ import javascript
|
||||
module XssThroughDom {
|
||||
import Xss::XssThroughDom
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
private import semmle.javascript.security.dataflow.Xss::DomBasedXss as DomBasedXss
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
|
||||
/**
|
||||
* Gets an attribute name that could store user-controlled data.
|
||||
|
||||
@@ -7,7 +7,7 @@ import javascript
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
import Xss::XssThroughDom
|
||||
private import XssThroughDomCustomizations::XssThroughDom
|
||||
private import semmle.javascript.security.dataflow.Xss::DomBasedXss as DomBasedXss
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
private import semmle.javascript.security.dataflow.UnsafeJQueryPluginCustomizations::UnsafeJQueryPlugin as UnsafeJQuery
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user