Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
0e61558
Empty commit
jorgectf Jun 19, 2021
78deec8
Upload main structure and initial tests
jorgectf Jun 22, 2021
b9fa57f
Move tests to `test/`
jorgectf Jun 29, 2021
c3b3bde
Add `XMLParser` concept
jorgectf Jun 29, 2021
d475d52
Add partial modeling
jorgectf Jun 29, 2021
11f4c1c
Format tests
jorgectf Jul 22, 2021
b5e10b6
Write `(String|Bytes)IO` additional taint step
jorgectf Jul 22, 2021
068150b
Finish modeling
jorgectf Jul 22, 2021
0d2646f
Polish documentation
jorgectf Jul 23, 2021
61e873d
Polish tests
jorgectf Jul 24, 2021
b83b31c
Write qldocs
jorgectf Jul 24, 2021
1dd77f1
Fix undetected tests
jorgectf Jul 24, 2021
93c8529
Add `.expected`
jorgectf Jul 24, 2021
48bca5b
Fix references' link anchor
jorgectf Aug 25, 2021
21da603
Update `.qlref`
jorgectf Sep 7, 2021
61a81b6
Extend `.qlref`
jorgectf Sep 9, 2021
67fddda
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL Sep 28, 2021
9c286a1
Python: fix name of `.qhelp` file
RasmusWL Sep 28, 2021
e472814
Python: Fix XXE qhelp
RasmusWL Sep 28, 2021
8df3dab
Python: Adjust `.expected` with subpaths
RasmusWL Sep 28, 2021
15dfc6d
Fix `xml_sax_parser.py` good/bad naming
jorgectf Oct 16, 2021
5b66a15
Extend `mayBeDangerous()` QLDoc
jorgectf Oct 16, 2021
320a00b
Delete simple `API::Node`s
jorgectf Oct 16, 2021
be42470
Apply suggestions from code review
jorgectf Oct 16, 2021
c2046f1
Improve readability for `xmlDom()`
jorgectf Oct 16, 2021
f1a73e3
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf Oct 16, 2021
58bc110
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL Oct 28, 2021
066b400
Add `lxml.etree.XMLParser` missing `resolve_entities` dangerous case
jorgectf Oct 28, 2021
637901d
Make concepts instances of their ranges
jorgectf Nov 16, 2021
cb8e54e
Delete redundant `LXMLParser` dangerous check
jorgectf Nov 16, 2021
9ab6d21
Add forward type tracking test
jorgectf Jan 14, 2022
a1f8acc
Merge branch 'github:main' into jorgectf/python/deserialization
jorgectf Jan 31, 2022
080775c
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf Jan 31, 2022
d96eb01
Merge branch 'github:main' into jorgectf/python/deserialization
jorgectf Feb 4, 2022
43fde35
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf Feb 4, 2022
99e14d1
Merge branch 'github:main' into jorgectf/python/deserialization
jorgectf Feb 5, 2022
d2f07e4
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf Feb 5, 2022
8f9cd16
Update
jorgectf Feb 8, 2022
7c4a6a1
Test polish
jorgectf Feb 8, 2022
01ad25f
Apply `.getALocalSource()` and fix `xmltodict`'s `vulnerable` predicate
jorgectf Feb 8, 2022
b00051e
Update `.expected`
jorgectf Feb 8, 2022
85b5ef3
`XmlInjection` -> `XmlEntityInjection`
jorgectf Feb 9, 2022
c5f30d9
Create an extendable `AdditionalTaintStep` class in customizations
jorgectf Feb 20, 2022
518e2ae
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL Mar 1, 2022
500e0ac
Python: Rewrite sax XML tests
RasmusWL Mar 1, 2022
ee23c05
Python: XML: Expose vuln kind on sink
RasmusWL Mar 1, 2022
aaf55b2
Python: Add XMLVulnerabilityKind
RasmusWL Mar 2, 2022
16e482b
Python: Improve QLDoc for XML parsing/parsers
RasmusWL Mar 2, 2022
6dd776b
Python: Only produce one alert per vulnerable XML sink
RasmusWL Mar 2, 2022
7f7758b
Python: rewrite xml sax modeling
RasmusWL Mar 2, 2022
515b824
Python: Add lxml positive test
RasmusWL Mar 3, 2022
661d8bf
Python: Better handling of `resolve_entities` arg in lxml
RasmusWL Mar 3, 2022
52891cb
Python: Add PoC for XML vulns
RasmusWL Mar 3, 2022
3c321dd
Python: Model `lxml.etree.get_default_parser` in own class
RasmusWL Mar 3, 2022
124c03c
Python: Expand lxml tests
RasmusWL Mar 3, 2022
e295399
Python: Properly handle `huge_tree` in lxml
RasmusWL Mar 3, 2022
703e3e8
Python: Handle DTD retrieval vuln in lxml
RasmusWL Mar 3, 2022
6129193
Python: Properly model `xml.etree`
RasmusWL Mar 3, 2022
3affa6c
Python: Annotate xmltodict tests
RasmusWL Mar 3, 2022
c4d08db
Python: Expand XML PoC with minidom/pulldom/expat
RasmusWL Mar 3, 2022
5a65248
Python: Annotate xml.dom tests
RasmusWL Mar 3, 2022
9406a97
Python: Fix vuln detection for xml.minidom with parser arg
RasmusWL Mar 3, 2022
7cda901
Python: Add separate query for SimpleXMLRPCServer
RasmusWL Mar 3, 2022
4b03f5c
Python: Rename xml.sax test for consistency
RasmusWL Mar 3, 2022
faebaee
Python: Use concept tests for XML Parsing
RasmusWL Mar 3, 2022
a7134ca
Python: Port xml.dom tests
RasmusWL Mar 3, 2022
5fb4c4d
Python: Port xml.etree tests
RasmusWL Mar 3, 2022
0b12d91
Python: Port xml.sax tests
RasmusWL Mar 3, 2022
c739ae4
Python: Port `xmltodict` tests
RasmusWL Mar 3, 2022
2451123
Python: Move XML PoC to new test dir
RasmusWL Mar 3, 2022
3278793
Python: Handle more functions and kw-args
RasmusWL Mar 3, 2022
f72f673
Python: Update `XmlEntityInjection.expected`
RasmusWL Mar 3, 2022
33ebcdf
Python: Support feed method of lxml/xml.etree Parsers
RasmusWL Mar 3, 2022
46238d5
Python: Add test for XMLPullParser
RasmusWL Mar 3, 2022
de0e67f
Python: Restructure overall XML modeling
RasmusWL Mar 3, 2022
a033b71
Python: Align QLdocs of XML modeling
RasmusWL Mar 3, 2022
c0a2c25
Python: Restructure modeling of `xml.etree` parsers
RasmusWL Mar 3, 2022
c0a6f9f
Python: Restructure lxml modeling
RasmusWL Mar 3, 2022
df8e0fc
Python: Minor fixup of qldoc
RasmusWL Mar 3, 2022
837daaa
Python: Remove XMLParser concept
RasmusWL Mar 3, 2022
0d69dc8
Python: Minor qldoc improvement
RasmusWL Mar 3, 2022
3f6c55e
Python: Rename `vulnerable` predicate => `vulnerableTo`
RasmusWL Mar 3, 2022
683c2fa
Apply suggestions from code review
jorgectf Mar 4, 2022
3cd165d
Python: Apply suggestions from code review
RasmusWL Mar 4, 2022
d6cbfec
Python: huge_tree tests were wrong
RasmusWL Mar 4, 2022
f0131af
Python: Fix `huge_tree` modeling
RasmusWL Mar 4, 2022
1a9620a
Python: Add conditional assignment check for sax parser
RasmusWL Mar 4, 2022
ef045a6
Python: Fix typo in set_default_parser
RasmusWL Mar 4, 2022
5552834
Merge pull request #9 from RasmusWL/WIP
jorgectf Mar 4, 2022
6b14c1d
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL Mar 8, 2022
0e9da4a
Python: Resolve name conflict over `XML` module
RasmusWL Mar 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Upload main structure and initial tests
  • Loading branch information
jorgectf committed Jun 22, 2021
commit 78deec84fc8ebd17e4aed6d108455e9aa3c58fce
20 changes: 20 additions & 0 deletions python/ql/src/experimental/Security/CWE-611/XXE.ql
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/Security/CWE-611/XXE.ql
63 changes: 63 additions & 0 deletions python/ql/src/experimental/Security/CWE-611/unit_tests/general.py
Original file line number Diff line number Diff line change
@@ -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)


Original file line number Diff line number Diff line change
@@ -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



43 changes: 43 additions & 0 deletions python/ql/src/experimental/semmle/python/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Comment thread
jorgectf marked this conversation as resolved.
Outdated
}
}

/**
* 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() }
}
1 change: 1 addition & 0 deletions python/ql/src/experimental/semmle/python/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*/

private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.XML
26 changes: 26 additions & 0 deletions python/ql/src/experimental/semmle/python/security/XXE.qll
Original file line number Diff line number Diff line change
@@ -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
}
}
Empty file.