Upload main structure and initial tests

This commit is contained in:
jorgectf
2021-06-22 16:41:08 +02:00
parent 0e61558644
commit 78deec84fc
8 changed files with 220 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
/**
* @name XML External Entity abuse
* @description User input should not be parsed by XML parsers without security options enabled.
* @kind path-problem
* @problem.severity error
* @id py/xxe
* @tags security
* external/cwe/cwe-611
*/
// determine precision above
import python
import experimental.semmle.python.security.XXE
import DataFlow::PathGraph
from XXEFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"$@ XML input is constructed from a $@ and isn't secured against XML External Entities abuse",
sink.getNode(), "This", source.getNode(), "user-provided value"

View File

@@ -0,0 +1 @@
experimental/Security/CWE-611/XXE.ql

View File

@@ -0,0 +1,63 @@
from flask import request, Flask
from io import StringIO
import xml.etree, xml.etree.ElementTree
import lxml.etree
import xml.dom.minidom, xml.dom.pulldom
import xmltodict
'''
XML Parsers:
xml.etree.ElementTree.XMLParser() - no options, vuln by default
lxml.etree.XMLParser() - no_network=True huge_tree=False resolve_entities=True
lxml.etree.get_default_parser() - no options, default above options
xml.sax.make_parser() - parser.setFeature(xml.sax.handler.feature_external_ges, True)
XML Parsing:
string:
xml.etree.ElementTree.fromstring(list)
xml.etree.ElementTree.XML
lxml.etree.fromstring(list)
lxml.etree.XML
xmltodict.parse
file StringIO(), BytesIO(b):
xml.etree.ElementTree.parse
lxml.etree.parse
xml.dom.(mini|pull)dom.parse(String)
'''
@app.route("/XMLParser-Empty&xml.etree.ElementTree.fromstring")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
parser = lxml.etree.XMLParser()
return xml.etree.ElementTree.fromstring(xml_content, parser=parser).text # 'root...'
@app.route("/XMLParser-Empty&xml.etree.ElementTree.parse")#!
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
parser = lxml.etree.XMLParser()
return xml.etree.ElementTree.parse(StringIO(xml_content), parser=parser).getroot().text # 'jorgectf'
@app.route("/XMLParser-Empty&lxml.etree.fromstring")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
parser = lxml.etree.XMLParser()
return lxml.etree.fromstring(xml_content, parser=parser).text # 'jorgectf'
@app.route("/XMLParser-Empty&xml.etree.parse")#!
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
parser = lxml.etree.XMLParser()
return lxml.etree.parse(StringIO(xml_content), parser=parser).getroot().text # 'jorgectf'
@app.route("/xmltodict-disable_entities_False")
def test2():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
return xmltodict.parse(xml_content, disable_entities=False)

View File

@@ -0,0 +1,66 @@
from io import StringIO
import xml.sax
# https://docs.python.org/3/library/xml.sax.handler.html#xml.sax.handler.feature_external_ges
class MainHandler(xml.sax.ContentHandler):
def __init__(self):
self._result = []
def characters(self, data):
self._result.append(data)
def parse(self, f):
xml.sax.parse(f, self)
return self._result
# GOOD
@app.route("/MainHandler")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
return MainHandler().parse(StringIO(xml_content))
@app.route("/xml.sax.make_parser()+MainHandler")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
BadHandler = MainHandler()
parser = xml.sax.make_parser()
parser.setContentHandler(BadHandler)
parser.parse(StringIO(xml_content))
return BadHandler._result
@app.route("/xml.sax.make_parser()+MainHandler-xml.sax.handler.feature_external_ges_False")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
BadHandler = MainHandler()
parser = xml.sax.make_parser()
parser.setContentHandler(BadHandler)
parser.setFeature(xml.sax.handler.feature_external_ges, False)
parser.parse(StringIO(xml_content))
return BadHandler._result
# BAD
@app.route("/xml.sax.make_parser()+MainHandler-xml.sax.handler.feature_external_ges_True")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
GoodHandler = MainHandler()
parser = xml.sax.make_parser()
parser.setContentHandler(GoodHandler)
parser.setFeature(xml.sax.handler.feature_external_ges, True)
parser.parse(StringIO(xml_content))
return GoodHandler._result
@app.route("/xml.sax.make_parser()+xml.dom.minidom.parse-xml.sax.handler.feature_external_ges_True")
def test1():
xml_content = request.args['xml_content'] # <?xml version="1.0"?><!DOCTYPE dt [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>
parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_external_ges, True)
return xml.dom.minidom.parse(StringIO(xml_content), parser=parser).documentElement.childNodes

View File

@@ -13,3 +13,46 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
/** Provides classes for modeling XML parsing APIs. */
module XMLParsing {
/**
* A data-flow node that collects functions parsing XML.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XMLParsing` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the content to parse.
*/
abstract DataFlow::Node getAnInput();
/**
* Holds if the parser may be parsing the input dangerously.
*/
abstract predicate mayBeDangerous();
}
}
/**
* A data-flow node that collects functions setting HTTP Headers' content.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XMLParsing` instead.
*/
class XMLParsing extends DataFlow::Node {
XMLParsing::Range range;
XMLParsing() { this = range }
/**
* Gets the argument containing the content to parse.
*/
DataFlow::Node getAnInput() { result = range.getAnInput() }
/**
* Holds if the parser may be parsing the input dangerously.
*/
predicate mayBeDangerous() { range.mayBeDangerous() }
}

View File

@@ -3,3 +3,4 @@
*/
private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.XML

View File

@@ -0,0 +1,26 @@
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.BarrierGuards
/**
* A taint-tracking configuration for detecting XML External entities abuse.
*
* This configuration uses `RemoteFlowSource` as a source because there's no
* risk at parsing not user-supplied input without security options enabled.
*/
class XXEFlowConfig extends TaintTracking::Configuration {
XXEFlowConfig() { this = "XXEFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(XMLParsing xmlParsing | xmlParsing.mayBeDangerous() and sink = xmlParsing.getAnInput())
}
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof StringConstCompare
}
}