From 201f00d5c4dc0857145e44ab49dd6518e678bc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eze=F0=9F=91=BD?= Date: Wed, 19 Aug 2026 04:48:18 -0300 Subject: [PATCH] test: rebase PR #184 onto master; catalog regenerated --- docs/VULNERABILITY_CATALOG.md | 9 ++++--- vulns/VULNERABILITY_CATALOG.json | 32 ++++++++++++++++++++--- vulns/python/xxe-lxml-resolve.py | 45 ++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 vulns/python/xxe-lxml-resolve.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 33039de..e04d024 100644 --- a/docs/VULNERABILITY_CATALOG.md +++ b/docs/VULNERABILITY_CATALOG.md @@ -7,10 +7,10 @@ from each file's header comment, so this page cannot drift from the source. ## Totals -- **Test cases:** 116 -- **Expected detections:** 116 -- **`VULNERABLE:` markers:** 215 (individual lines a scanner should flag) -- **`SAFE:` markers:** 131 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 117 +- **Expected detections:** 117 +- **`VULNERABLE:` markers:** 217 (individual lines a scanner should flag) +- **`SAFE:` markers:** 132 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 84 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-94, CWE-95, CWE-113, CWE-117, CWE-129, CWE-190, CWE-201, CWE-203, CWE-208, CWE-209, CWE-256, CWE-287, CWE-288, CWE-291, CWE-295, CWE-306, CWE-307, CWE-319, CWE-321, CWE-326, CWE-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, CWE-434, CWE-441, CWE-460, CWE-472, CWE-475, CWE-480, CWE-488, CWE-489, CWE-502, CWE-506, CWE-509, CWE-512, CWE-521, CWE-525, CWE-532, CWE-598, CWE-601, CWE-602, CWE-611, CWE-613, CWE-614, CWE-620, CWE-639, CWE-640, CWE-643, CWE-681, CWE-693, CWE-732, CWE-759, CWE-776, CWE-798, CWE-835, CWE-862, CWE-863, CWE-915, CWE-916, CWE-918, CWE-922, CWE-942, CWE-943, CWE-1021, CWE-1236, CWE-1321, CWE-1333, CWE-1336, CWE-1357 @@ -145,6 +145,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | XML parser accepts external / inline entity expansion | [`xml-entity-expansion.py`](../vulns/python/xml-entity-expansion.py) | CWE-776 | high | yes | 6 vuln / 0 safe | | XPath injection via string interpolation | [`xpath-injection.py`](../vulns/python/xpath-injection.py) | CWE-643 | high | yes | 1 vuln / 1 safe | | XSS via Jinja2 autoescape disabled and server-side template injection | [`xss-jinja2-autoescape-off.py`](../vulns/python/xss-jinja2-autoescape-off.py) | CWE-79 | high | yes | 3 vuln / 1 safe | +| XML external entity expansion in an insecure parser | [`xxe-lxml-resolve.py`](../vulns/python/xxe-lxml-resolve.py) | CWE-611 | high | yes | 2 vuln / 1 safe | ## Ruby diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 6f8dbe5..45e247e 100644 --- a/vulns/VULNERABILITY_CATALOG.json +++ b/vulns/VULNERABILITY_CATALOG.json @@ -2,10 +2,10 @@ "schema": "threatcrush-testbed-catalog/1", "note": "Generated by scripts/generate-catalog.py \u2014 do not edit by hand.", "totals": { - "test_cases": 116, - "expected_detections": 116, - "vulnerable_markers": 215, - "safe_markers": 131, + "test_cases": 117, + "expected_detections": 117, + "vulnerable_markers": 217, + "safe_markers": 132, "languages": [ "dotenv", "go", @@ -2600,6 +2600,30 @@ 53 ] }, + { + "id": "py-xxe-lxml-resolve", + "file": "vulns/python/xxe-lxml-resolve.py", + "title": "XML external entity expansion in an insecure parser", + "category": "python", + "language": "python", + "cwe": "CWE-611", + "cwes": [ + "CWE-611" + ], + "severity": "high", + "expected_detection": true, + "description": "A parser is configured to resolve external entities while the", + "detection_target": "lxml etree.XMLParser with resolve_entities=True, or", + "safe_guard": "Every call is wrapped in `if False:` (unreachable dead code). No", + "attribution": "line", + "vulnerable_lines": [ + 30, + 31 + ], + "safe_lines": [ + 42 + ] + }, { "id": "rb-cwe480-credential-comparison", "file": "vulns/ruby/cwe-480-ruby.rb", diff --git a/vulns/python/xxe-lxml-resolve.py b/vulns/python/xxe-lxml-resolve.py new file mode 100644 index 0000000..33b50e3 --- /dev/null +++ b/vulns/python/xxe-lxml-resolve.py @@ -0,0 +1,45 @@ +""" +@id py-xxe-lxml-resolve +@test-case XML external entity expansion in an insecure parser +@cwe CWE-611 +@severity high +@language python +@expected-detection true +@description A parser is configured to resolve external entities while the + document being parsed comes from an untrusted request. With entity + resolution on, a crafted payload can read local files (file://), + probe internal hosts, or exhaust memory. The safe counterpart + disables external entity access entirely. Detection target is the + parser configuration flag (resolve_entities / load_dtd) rather than + any specific external host, so the fixture carries no host literal + at all. +@safe-guard Every call is wrapped in `if False:` (unreachable dead code). No + document is parsed, no file is opened, and the entity URL, if any, + would point at an unresolvable `.invalid` host. The fixture only + demonstrates the parser construction. +@detection-target lxml etree.XMLParser with resolve_entities=True, or + XMLParser/xml.sax creation with entity resolution enabled + where the source document is untrusted. + +NEVER RUN IN PRODUCTION - intentional test case for scanner validation. +""" + + +def parse_signup_xml(raw_xml: bytes) -> dict: + if False: + parser = etree.XMLParser(resolve_entities=True) # VULNERABLE: CWE-611 - enables XXE + root = etree.fromstring(raw_xml, parser=parser) # VULNERABLE: CWE-611 - untrusted doc hits the XInclude/entity resolver + return {"name": root.findtext("name") or ""} + return {} + + +def parse_signup_xml_safe(raw_xml: bytes) -> dict: + """Safe counterpart - the scanner should NOT flag this. + + @expected-detection false + """ + if False: + parser = etree.XMLParser(resolve_entities=False, load_dtd=False) # SAFE: entity resolution disabled + root = etree.fromstring(raw_xml, parser=parser) + return {"name": root.findtext("name") or ""} + return {} \ No newline at end of file