diff --git a/python/ql/src/experimental/Security/NEW/CWE-611/Xxe.qhelp b/python/ql/src/experimental/Security/NEW/CWE-611/Xxe.qhelp new file mode 100644 index 00000000000..1e859eb121f --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-611/Xxe.qhelp @@ -0,0 +1,57 @@ + + + + +

+Parsing untrusted XML files with a weakly configured XML parser may lead to an +XML External Entity (XXE) attack. This type of attack uses external entity references +to access arbitrary files on a system, carry out denial-of-service (DoS) attacks, or server-side +request forgery. Even when the result of parsing is not returned to the user, DoS attacks are still possible +and out-of-band data retrieval techniques may allow attackers to steal sensitive data. +

+
+ + +

+The easiest way to prevent XXE attacks is to disable external entity handling when +parsing untrusted data. How this is done depends on the library being used. Note that some +libraries, such as recent versions of libxml, disable entity expansion by default, +so unless you have explicitly enabled entity expansion, no further action needs to be taken. +

+
+ + +

+The following example uses the libxml XML parser to parse a string xmlSrc. +If that string is from an untrusted source, this code may be vulnerable to an XXE attack, since +the parser is invoked with the noent option set to true: +

+ + +

+To guard against XXE attacks, the noent option should be omitted or set to +false. This means that no entity expansion is undertaken at all, not even for standard +internal entities such as & or >. If desired, these +entities can be expanded in a separate step using utility functions provided by libraries such +as underscore, +lodash or +he. +

+ +
+ + +
  • +OWASP: +XML External Entity (XXE) Processing. +
  • +
  • +Timothy Morgen: +XML Schema, DTD, and Entity Attacks. +
  • +
  • +Timur Yunusov, Alexey Osipov: +XML Out-Of-Band Data Retrieval. +
  • +
    +
    diff --git a/python/ql/src/experimental/Security/NEW/CWE-611/Xxe.ql b/python/ql/src/experimental/Security/NEW/CWE-611/Xxe.ql new file mode 100644 index 00000000000..01e518b6df7 --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-611/Xxe.ql @@ -0,0 +1,23 @@ +/** + * @name XML external entity expansion + * @description Parsing user input as an XML document with external + * entity expansion is vulnerable to XXE attacks. + * @kind path-problem + * @problem.severity error + * @security-severity 9.1 + * @precision high + * @id js/xxe + * @tags security + * external/cwe/cwe-611 + * external/cwe/cwe-827 + */ + +import javascript +import semmle.javascript.security.dataflow.XxeQuery +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, + "A $@ is parsed as XML without guarding against external entity expansion.", source.getNode(), + "user-provided value" diff --git a/python/ql/src/experimental/Security/NEW/CWE-611/examples/Xxe.js b/python/ql/src/experimental/Security/NEW/CWE-611/examples/Xxe.js new file mode 100644 index 00000000000..99fa02cc42f --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-611/examples/Xxe.js @@ -0,0 +1,7 @@ +const app = require("express")(), + libxml = require("libxmljs"); + +app.post("upload", (req, res) => { + let xmlSrc = req.body, + doc = libxml.parseXml(xmlSrc, { noent: true }); +}); diff --git a/python/ql/src/experimental/Security/NEW/CWE-611/examples/XxeGood.js b/python/ql/src/experimental/Security/NEW/CWE-611/examples/XxeGood.js new file mode 100644 index 00000000000..8317dcac98f --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-611/examples/XxeGood.js @@ -0,0 +1,7 @@ +const app = require("express")(), + libxml = require("libxmljs"); + +app.post("upload", (req, res) => { + let xmlSrc = req.body, + doc = libxml.parseXml(xmlSrc); +}); diff --git a/python/ql/src/experimental/Security/NEW/CWE-776/XmlBomb.qhelp b/python/ql/src/experimental/Security/NEW/CWE-776/XmlBomb.qhelp new file mode 100644 index 00000000000..c0714b3f96f --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-776/XmlBomb.qhelp @@ -0,0 +1,60 @@ + + + + +

    +Parsing untrusted XML files with a weakly configured XML parser may be vulnerable to +denial-of-service (DoS) attacks exploiting uncontrolled internal entity expansion. +

    +

    +In XML, so-called internal entities are a mechanism for introducing an abbreviation +for a piece of text or part of a document. When a parser that has been configured +to expand entities encounters a reference to an internal entity, it replaces the entity +by the data it represents. The replacement text may itself contain other entity references, +which are expanded recursively. This means that entity expansion can increase document size +dramatically. +

    +

    +If untrusted XML is parsed with entity expansion enabled, a malicious attacker could +submit a document that contains very deeply nested entity definitions, causing the parser +to take a very long time or use large amounts of memory. This is sometimes called an +XML bomb attack. +

    +
    + + +

    +The safest way to prevent XML bomb attacks is to disable entity expansion when parsing untrusted +data. How this is done depends on the library being used. Note that some libraries, such as +recent versions of libxmljs (though not its SAX parser API), disable entity expansion +by default, so unless you have explicitly enabled entity expansion, no further action is needed. +

    +
    + + +

    +The following example uses the XML parser provided by the node-expat package to +parse a string xmlSrc. If that string is from an untrusted source, this code may be +vulnerable to a DoS attack, since node-expat expands internal entities by default: +

    + + +

    +At the time of writing, node-expat does not provide a way of controlling entity +expansion, but the example could be rewritten to use the sax package instead, +which only expands standard entities such as &: +

    + +
    + + +
  • +Wikipedia: +Billion Laughs. +
  • +
  • +Bryan Sullivan: +Security Briefs - XML Denial of Service Attacks and Defenses. +
  • +
    +
    diff --git a/python/ql/src/experimental/Security/NEW/CWE-776/XmlBomb.ql b/python/ql/src/experimental/Security/NEW/CWE-776/XmlBomb.ql new file mode 100644 index 00000000000..c340eee68cc --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-776/XmlBomb.ql @@ -0,0 +1,23 @@ +/** + * @name XML internal entity expansion + * @description Parsing user input as an XML document with arbitrary internal + * entity expansion is vulnerable to denial-of-service attacks. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.5 + * @precision high + * @id js/xml-bomb + * @tags security + * external/cwe/cwe-776 + * external/cwe/cwe-400 + */ + +import javascript +import semmle.javascript.security.dataflow.XmlBombQuery +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, + "A $@ is parsed as XML without guarding against uncontrolled entity expansion.", source.getNode(), + "user-provided value" diff --git a/python/ql/src/experimental/Security/NEW/CWE-776/examples/XmlBomb.js b/python/ql/src/experimental/Security/NEW/CWE-776/examples/XmlBomb.js new file mode 100644 index 00000000000..f72902a5304 --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-776/examples/XmlBomb.js @@ -0,0 +1,10 @@ +const app = require("express")(), + expat = require("node-expat"); + +app.post("upload", (req, res) => { + let xmlSrc = req.body, + parser = new expat.Parser(); + parser.on("startElement", handleStart); + parser.on("text", handleText); + parser.write(xmlSrc); +}); diff --git a/python/ql/src/experimental/Security/NEW/CWE-776/examples/XmlBombGood.js b/python/ql/src/experimental/Security/NEW/CWE-776/examples/XmlBombGood.js new file mode 100644 index 00000000000..a8c5bc97e63 --- /dev/null +++ b/python/ql/src/experimental/Security/NEW/CWE-776/examples/XmlBombGood.js @@ -0,0 +1,10 @@ +const app = require("express")(), + sax = require("sax"); + +app.post("upload", (req, res) => { + let xmlSrc = req.body, + parser = sax.parser(true); + parser.onopentag = handleStart; + parser.ontext = handleText; + parser.write(xmlSrc); +}); diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/XmlBombCustomizations.qll b/python/ql/src/experimental/semmle/python/security/dataflow/XmlBombCustomizations.qll new file mode 100644 index 00000000000..1d159b057ad --- /dev/null +++ b/python/ql/src/experimental/semmle/python/security/dataflow/XmlBombCustomizations.qll @@ -0,0 +1,49 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * XML-bomb vulnerabilities, as well as extension points for adding + * your own. + */ + +import javascript +import semmle.javascript.security.dataflow.DOM + +module XmlBomb { + /** + * A data flow source for XML-bomb vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for XML-bomb vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for XML-bomb vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** A source of remote user input, considered as a flow source for XML bomb vulnerabilities. */ + class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * An access to `document.location`, considered as a flow source for XML bomb vulnerabilities. + */ + class LocationAsSource extends Source, DataFlow::ValueNode { + LocationAsSource() { isLocation(astNode) } + } + + /** + * A call to an XML parser that performs internal entity expansion, viewed + * as a data flow sink for XML-bomb vulnerabilities. + */ + class XmlParsingWithEntityResolution extends Sink, DataFlow::ValueNode { + XmlParsingWithEntityResolution() { + exists(XML::ParserInvocation parse | astNode = parse.getSourceArgument() | + parse.resolvesEntities(XML::InternalEntity()) + ) + } + } +} diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/XmlBombQuery.qll b/python/ql/src/experimental/semmle/python/security/dataflow/XmlBombQuery.qll new file mode 100644 index 00000000000..951b927f86e --- /dev/null +++ b/python/ql/src/experimental/semmle/python/security/dataflow/XmlBombQuery.qll @@ -0,0 +1,27 @@ +/** + * Provides a taint tracking configuration for reasoning about + * XML-bomb vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `XmlBomb::Configuration` is needed, otherwise + * `XmlBombCustomizations` should be imported instead. + */ + +import javascript +import XmlBombCustomizations::XmlBomb + +/** + * A taint-tracking configuration for reasoning about XML-bomb vulnerabilities. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "XmlBomb" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } +} diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/XxeCustomizations.qll b/python/ql/src/experimental/semmle/python/security/dataflow/XxeCustomizations.qll new file mode 100644 index 00000000000..4e7bb5e730c --- /dev/null +++ b/python/ql/src/experimental/semmle/python/security/dataflow/XxeCustomizations.qll @@ -0,0 +1,52 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * XML External Entity (XXE) vulnerabilities, as well as extension + * points for adding your own. + */ + +import javascript +import semmle.javascript.security.dataflow.DOM + +module Xxe { + /** + * A data flow source for XXE vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for XXE vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for XXE vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** A source of remote user input, considered as a flow source for XXE vulnerabilities. */ + class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * An access to `document.location`, considered as a flow source for XXE vulnerabilities. + */ + class LocationAsSource extends Source, DataFlow::ValueNode { + LocationAsSource() { isLocation(astNode) } + } + + /** + * A call to an XML parser that performs external entity expansion, viewed + * as a data flow sink for XXE vulnerabilities. + */ + class XmlParsingWithExternalEntityResolution extends Sink, DataFlow::ValueNode { + XmlParsingWithExternalEntityResolution() { + exists(XML::ParserInvocation parse | astNode = parse.getSourceArgument() | + parse.resolvesEntities(XML::ExternalEntity(_)) + or + parse.resolvesEntities(XML::ParameterEntity(true)) and + parse.resolvesEntities(XML::InternalEntity()) + ) + } + } +} diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/XxeQuery.qll b/python/ql/src/experimental/semmle/python/security/dataflow/XxeQuery.qll new file mode 100644 index 00000000000..82d3fb4f6cc --- /dev/null +++ b/python/ql/src/experimental/semmle/python/security/dataflow/XxeQuery.qll @@ -0,0 +1,27 @@ +/** + * Provides a taint tracking configuration for reasoning about XML + * External Entity (XXE) vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `Xxe::Configuration` is needed, otherwise `XxeCustomizations` + * should be imported instead. + */ + +import javascript +import XxeCustomizations::Xxe + +/** + * A taint-tracking configuration for reasoning about XXE vulnerabilities. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "Xxe" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } +}