-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Python: Port and extend XXE modeling #6112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
RasmusWL
merged 91 commits into
github:main
from
jorgectf:jorgectf/python/deserialization
Mar 14, 2022
Merged
Changes from 1 commit
Commits
Show all changes
91 commits
Select commit
Hold shift + click to select a range
0e61558
Empty commit
jorgectf 78deec8
Upload main structure and initial tests
jorgectf b9fa57f
Move tests to `test/`
jorgectf c3b3bde
Add `XMLParser` concept
jorgectf d475d52
Add partial modeling
jorgectf 11f4c1c
Format tests
jorgectf b5e10b6
Write `(String|Bytes)IO` additional taint step
jorgectf 068150b
Finish modeling
jorgectf 0d2646f
Polish documentation
jorgectf 61e873d
Polish tests
jorgectf b83b31c
Write qldocs
jorgectf 1dd77f1
Fix undetected tests
jorgectf 93c8529
Add `.expected`
jorgectf 48bca5b
Fix references' link anchor
jorgectf 21da603
Update `.qlref`
jorgectf 61a81b6
Extend `.qlref`
jorgectf 67fddda
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL 9c286a1
Python: fix name of `.qhelp` file
RasmusWL e472814
Python: Fix XXE qhelp
RasmusWL 8df3dab
Python: Adjust `.expected` with subpaths
RasmusWL 15dfc6d
Fix `xml_sax_parser.py` good/bad naming
jorgectf 5b66a15
Extend `mayBeDangerous()` QLDoc
jorgectf 320a00b
Delete simple `API::Node`s
jorgectf be42470
Apply suggestions from code review
jorgectf c2046f1
Improve readability for `xmlDom()`
jorgectf f1a73e3
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf 58bc110
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL 066b400
Add `lxml.etree.XMLParser` missing `resolve_entities` dangerous case
jorgectf 637901d
Make concepts instances of their ranges
jorgectf cb8e54e
Delete redundant `LXMLParser` dangerous check
jorgectf 9ab6d21
Add forward type tracking test
jorgectf a1f8acc
Merge branch 'github:main' into jorgectf/python/deserialization
jorgectf 080775c
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf d96eb01
Merge branch 'github:main' into jorgectf/python/deserialization
jorgectf 43fde35
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf 99e14d1
Merge branch 'github:main' into jorgectf/python/deserialization
jorgectf d2f07e4
Merge branch 'jorgectf/python/deserialization' of https://github.com/…
jorgectf 8f9cd16
Update
jorgectf 7c4a6a1
Test polish
jorgectf 01ad25f
Apply `.getALocalSource()` and fix `xmltodict`'s `vulnerable` predicate
jorgectf b00051e
Update `.expected`
jorgectf 85b5ef3
`XmlInjection` -> `XmlEntityInjection`
jorgectf c5f30d9
Create an extendable `AdditionalTaintStep` class in customizations
jorgectf 518e2ae
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL 500e0ac
Python: Rewrite sax XML tests
RasmusWL ee23c05
Python: XML: Expose vuln kind on sink
RasmusWL aaf55b2
Python: Add XMLVulnerabilityKind
RasmusWL 16e482b
Python: Improve QLDoc for XML parsing/parsers
RasmusWL 6dd776b
Python: Only produce one alert per vulnerable XML sink
RasmusWL 7f7758b
Python: rewrite xml sax modeling
RasmusWL 515b824
Python: Add lxml positive test
RasmusWL 661d8bf
Python: Better handling of `resolve_entities` arg in lxml
RasmusWL 52891cb
Python: Add PoC for XML vulns
RasmusWL 3c321dd
Python: Model `lxml.etree.get_default_parser` in own class
RasmusWL 124c03c
Python: Expand lxml tests
RasmusWL e295399
Python: Properly handle `huge_tree` in lxml
RasmusWL 703e3e8
Python: Handle DTD retrieval vuln in lxml
RasmusWL 6129193
Python: Properly model `xml.etree`
RasmusWL 3affa6c
Python: Annotate xmltodict tests
RasmusWL c4d08db
Python: Expand XML PoC with minidom/pulldom/expat
RasmusWL 5a65248
Python: Annotate xml.dom tests
RasmusWL 9406a97
Python: Fix vuln detection for xml.minidom with parser arg
RasmusWL 7cda901
Python: Add separate query for SimpleXMLRPCServer
RasmusWL 4b03f5c
Python: Rename xml.sax test for consistency
RasmusWL faebaee
Python: Use concept tests for XML Parsing
RasmusWL a7134ca
Python: Port xml.dom tests
RasmusWL 5fb4c4d
Python: Port xml.etree tests
RasmusWL 0b12d91
Python: Port xml.sax tests
RasmusWL c739ae4
Python: Port `xmltodict` tests
RasmusWL 2451123
Python: Move XML PoC to new test dir
RasmusWL 3278793
Python: Handle more functions and kw-args
RasmusWL f72f673
Python: Update `XmlEntityInjection.expected`
RasmusWL 33ebcdf
Python: Support feed method of lxml/xml.etree Parsers
RasmusWL 46238d5
Python: Add test for XMLPullParser
RasmusWL de0e67f
Python: Restructure overall XML modeling
RasmusWL a033b71
Python: Align QLdocs of XML modeling
RasmusWL c0a2c25
Python: Restructure modeling of `xml.etree` parsers
RasmusWL c0a6f9f
Python: Restructure lxml modeling
RasmusWL df8e0fc
Python: Minor fixup of qldoc
RasmusWL 837daaa
Python: Remove XMLParser concept
RasmusWL 0d69dc8
Python: Minor qldoc improvement
RasmusWL 3f6c55e
Python: Rename `vulnerable` predicate => `vulnerableTo`
RasmusWL 683c2fa
Apply suggestions from code review
jorgectf 3cd165d
Python: Apply suggestions from code review
RasmusWL d6cbfec
Python: huge_tree tests were wrong
RasmusWL f0131af
Python: Fix `huge_tree` modeling
RasmusWL 1a9620a
Python: Add conditional assignment check for sax parser
RasmusWL ef045a6
Python: Fix typo in set_default_parser
RasmusWL 5552834
Merge pull request #9 from RasmusWL/WIP
jorgectf 6b14c1d
Merge branch 'main' into jorgectf/python/deserialization
RasmusWL 0e9da4a
Python: Resolve name conflict over `XML` module
RasmusWL File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Upload main structure and initial tests
- Loading branch information
commit 78deec84fc8ebd17e4aed6d108455e9aa3c58fce
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
1 change: 1 addition & 0 deletions
1
python/ql/src/experimental/Security/CWE-611/unit_tests/XXE.qlref
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
63
python/ql/src/experimental/Security/CWE-611/unit_tests/general.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
|
|
||
|
|
66 changes: 66 additions & 0 deletions
66
python/ql/src/experimental/Security/CWE-611/unit_tests/xml_sax_make_parser.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.