From dd0cbc844a58012eed9ca60777575f1ebcc30d1f Mon Sep 17 00:00:00 2001 From: root Date: Mon, 3 Aug 2026 09:23:59 +0200 Subject: [PATCH 1/2] Add 3 test cases: Java XXE (CWE-611), JS CSRF (CWE-352), Python ReDoS (CWE-1333) --- docs/VULNERABILITY_CATALOG.md | 13 ++-- vulns/VULNERABILITY_CATALOG.json | 85 ++++++++++++++++++++++-- vulns/java/xxe-document-builder.java | 54 +++++++++++++++ vulns/javascript/csrf-missing-token.js | 76 +++++++++++++++++++++ vulns/python/redos-catastrophic-regex.py | 47 +++++++++++++ 5 files changed, 266 insertions(+), 9 deletions(-) create mode 100644 vulns/java/xxe-document-builder.java create mode 100644 vulns/javascript/csrf-missing-token.js create mode 100644 vulns/python/redos-catastrophic-regex.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index d9816ec..d151d91 100644 --- a/docs/VULNERABILITY_CATALOG.md +++ b/docs/VULNERABILITY_CATALOG.md @@ -7,12 +7,12 @@ from each file's header comment, so this page cannot drift from the source. ## Totals -- **Test cases:** 32 -- **Expected detections:** 32 -- **`VULNERABLE:` markers:** 77 (individual lines a scanner should flag) -- **`SAFE:` markers:** 40 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 35 +- **Expected detections:** 35 +- **`VULNERABLE:` markers:** 85 (individual lines a scanner should flag) +- **`SAFE:` markers:** 43 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text -- **CWE categories:** 19 — CWE-22, CWE-78, CWE-79, CWE-89, CWE-95, CWE-190, CWE-347, CWE-362, CWE-377, CWE-502, CWE-506, CWE-532, CWE-601, CWE-681, CWE-798, CWE-918, CWE-1321, CWE-1336, CWE-1357 +- **CWE categories:** 22 — CWE-22, CWE-78, CWE-79, CWE-89, CWE-95, CWE-190, CWE-347, CWE-352, CWE-362, CWE-377, CWE-502, CWE-506, CWE-532, CWE-601, CWE-611, CWE-681, CWE-798, CWE-918, CWE-1321, CWE-1333, CWE-1336, CWE-1357 ## How coverage is scored @@ -38,11 +38,13 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Insecure deserialisation via ObjectInputStream | [`deserialization-object-input-stream.java`](../vulns/java/deserialization-object-input-stream.java) | CWE-502 | critical | yes | 2 vuln / 2 safe | | SQL injection via Statement string concatenation | [`sqli-statement-concat.java`](../vulns/java/sqli-statement-concat.java) | CWE-89 | critical | yes | 2 vuln / 1 safe | | Reflected XSS via HttpServletResponse writer | [`xss-response-writer.java`](../vulns/java/xss-response-writer.java) | CWE-79 | high | yes | 2 vuln / 2 safe | +| XXE via unconfigured DocumentBuilderFactory | [`xxe-document-builder.java`](../vulns/java/xxe-document-builder.java) | CWE-611 | high | yes | 2 vuln / 1 safe | ## JavaScript | Test case | File | CWE | Severity | Expected | Markers | |---|---|---|---|---|---| +| CSRF via missing anti-CSRF token on state-changing POST | [`csrf-missing-token.js`](../vulns/javascript/csrf-missing-token.js) | CWE-352 | high | yes | 3 vuln / 1 safe | | JWT signature validation bypass via decode-only parsing | [`jwt-decode-without-verify.js`](../vulns/javascript/jwt-decode-without-verify.js) | CWE-347 | high | yes | 1 vuln / 1 safe | | Open redirect via unvalidated next parameter | [`open-redirect.js`](../vulns/javascript/open-redirect.js) | CWE-601 | medium | yes | 3 vuln / 1 safe | | Prototype pollution via recursive merge | [`prototype-pollution.js`](../vulns/javascript/prototype-pollution.js) | CWE-1321 | high | yes | 2 vuln / 1 safe | @@ -59,6 +61,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Path traversal via unvalidated filename in open() | [`path-traversal-open.py`](../vulns/python/path-traversal-open.py) | CWE-22 | high | yes | 2 vuln / 2 safe | | Insecure deserialisation via pickle / yaml.load | [`pickle-deserialization.py`](../vulns/python/pickle-deserialization.py) | CWE-502 | critical | yes | 2 vuln / 2 safe | | OS command injection via os.system / subprocess shell=True | [`rce-os-system.py`](../vulns/python/rce-os-system.py) | CWE-78 | critical | yes | 3 vuln / 1 safe | +| ReDoS via catastrophic backtracking regex on user input | [`redos-catastrophic-regex.py`](../vulns/python/redos-catastrophic-regex.py) | CWE-1333 | high | yes | 3 vuln / 1 safe | | SQL injection via string formatting | [`sqli-string-format.py`](../vulns/python/sqli-string-format.py) | CWE-89 | critical | yes | 3 vuln / 1 safe | | TOCTOU race condition in file access | [`toctou-race-condition.py`](../vulns/python/toctou-race-condition.py) | CWE-362 | medium | yes | 3 vuln / 2 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 | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 77940d7..081d305 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": 32, - "expected_detections": 32, - "vulnerable_markers": 77, - "safe_markers": 40, + "test_cases": 35, + "expected_detections": 35, + "vulnerable_markers": 85, + "safe_markers": 43, "languages": [ "dotenv", "go", @@ -33,16 +33,19 @@ "CWE-95", "CWE-190", "CWE-347", + "CWE-352", "CWE-362", "CWE-377", "CWE-502", "CWE-506", "CWE-532", "CWE-601", + "CWE-611", "CWE-681", "CWE-798", "CWE-918", "CWE-1321", + "CWE-1333", "CWE-1336", "CWE-1357" ] @@ -222,6 +225,55 @@ 56 ] }, + { + "id": "java-xxe-document-builder", + "file": "vulns/java/xxe-document-builder.java", + "title": "XXE via unconfigured DocumentBuilderFactory", + "category": "java", + "language": "java", + "cwe": "CWE-611", + "cwes": [ + "CWE-611" + ], + "severity": "high", + "expected_detection": true, + "description": "An XML parser is built from DocumentBuilderFactory with default", + "detection_target": "Taint flow from user XML input into DocumentBuilder.parse", + "safe_guard": "Wrapped in if (false) \u2014 the body is unreachable dead code. All", + "attribution": "line", + "vulnerable_lines": [ + 30, + 34 + ], + "safe_lines": [ + 46 + ] + }, + { + "id": "js-csrf-missing-token", + "file": "vulns/javascript/csrf-missing-token.js", + "title": "CSRF via missing anti-CSRF token on state-changing POST", + "category": "javascript", + "language": "javascript", + "cwe": "CWE-352", + "cwes": [ + "CWE-352" + ], + "severity": "high", + "expected_detection": true, + "description": "A state-changing Express endpoint (transfer, delete, password", + "detection_target": "State-changing handler that reads session cookies but never", + "safe_guard": "Wrapped in if (false) \u2014 the body is unreachable dead code. All", + "attribution": "line", + "vulnerable_lines": [ + 25, + 33, + 42 + ], + "safe_lines": [ + 55 + ] + }, { "id": "js-jwt-decode-without-verify", "file": "vulns/javascript/jwt-decode-without-verify.js", @@ -491,6 +543,31 @@ 47 ] }, + { + "id": "py-redos-catastrophic-regex", + "file": "vulns/python/redos-catastrophic-regex.py", + "title": "ReDoS via catastrophic backtracking regex on user input", + "category": "python", + "language": "python", + "cwe": "CWE-1333", + "cwes": [ + "CWE-1333" + ], + "severity": "high", + "expected_detection": true, + "description": "A user-supplied string is matched against a regex with nested", + "detection_target": "Regex pattern with nested quantifiers applied to", + "safe_guard": "Wrapped in if False \u2014 the body is unreachable dead code. All", + "attribution": "line", + "vulnerable_lines": [ + 22, + 29, + 35 + ], + "safe_lines": [ + 45 + ] + }, { "id": "py-sqli-string-format", "file": "vulns/python/sqli-string-format.py", diff --git a/vulns/java/xxe-document-builder.java b/vulns/java/xxe-document-builder.java new file mode 100644 index 0000000..c5be0e7 --- /dev/null +++ b/vulns/java/xxe-document-builder.java @@ -0,0 +1,54 @@ +/** + * @id java-xxe-document-builder + * @test-case XXE via unconfigured DocumentBuilderFactory + * @cwe CWE-611 + * @severity high + * @language java + * @expected-detection true + * @description An XML parser is built from DocumentBuilderFactory with default + * settings, so an attacker-supplied XML document can expand + * external entities and read local files (e.g. /etc/passwd) or + * cause billion-laughs resource exhaustion. + * @safe-guard Wrapped in if (false) — the body is unreachable dead code. All + * literal hosts use the RFC 2606 reserved .invalid TLD. + * @detection-target Taint flow from user XML input into DocumentBuilder.parse + * without secure processing features enabled. + */ + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.XMLConstants; +import org.w3c.dom.Document; +import java.io.ByteArrayInputStream; + +public class XxeDocumentBuilder { + + // NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + public static Document parseXmlVulnerable(String xml) throws Exception { + if (false) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // VULNERABLE: CWE-611 — external entities enabled by default + DocumentBuilder builder = factory.newDocumentBuilder(); + // SOURCE: attacker-controlled XML body + return builder.parse(new ByteArrayInputStream(xml.getBytes())); + // VULNERABLE: CWE-611 — parse() expands external entities + } + return null; + } + + /** + * Safe counterpart — the scanner should NOT flag this. + * @expected-detection false + */ + public static Document parseXmlSafe(String xml) throws Exception { + if (false) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // SAFE: XXE protections explicitly enabled + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(new ByteArrayInputStream(xml.getBytes())); + } + return null; + } +} diff --git a/vulns/javascript/csrf-missing-token.js b/vulns/javascript/csrf-missing-token.js new file mode 100644 index 0000000..c192600 --- /dev/null +++ b/vulns/javascript/csrf-missing-token.js @@ -0,0 +1,76 @@ +/** + * @id js-csrf-missing-token + * @test-case CSRF via missing anti-CSRF token on state-changing POST + * @cwe CWE-352 + * @severity high + * @language javascript + * @expected-detection true + * @description A state-changing Express endpoint (transfer, delete, password + * change) accepts POST requests with no CSRF token check. A + * malicious site can forge a cross-origin form submission and + * execute the action with the victim's session cookie. + * @safe-guard Wrapped in if (false) — the body is unreachable dead code. All + * literal hosts use the RFC 2606 reserved .invalid TLD. + * @detection-target State-changing handler that reads session cookies but never + * compares a token against the session. + */ + +'use strict'; + +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +function transferVulnerable(req, res) { + if (false) { + const amount = req.body.amount; // SOURCE: attacker-controlled cross-site form + const to = req.body.to; // SOURCE + // VULNERABLE: CWE-352 — no CSRF token check on state-changing POST + transferFunds(req.session.userId, to, amount); + return res.json({ ok: true }); + } +} + +function deleteAccountVulnerable(req, res) { + if (false) { + // VULNERABLE: CWE-352 — reads session cookie, mutates, no token verified + deleteUser(req.session.userId); + return res.send('deleted'); + } +} + +function changePasswordVulnerable(req, res) { + if (false) { + const newPw = req.body.password; + // VULNERABLE: CWE-352 — password change without CSRF protection + updatePassword(req.session.userId, newPw); + return res.send('ok'); + } +} + +/** + * Safe counterpart — the scanner should NOT flag this. + * @expected-detection false + */ +function transferSafe(req, res) { + if (false) { + const token = req.body.csrf_token; + // SAFE: token compared against session before mutation + if (token !== req.session.csrfToken) { + return res.status(403).send('invalid csrf'); + } + const amount = req.body.amount; + const to = req.body.to; + transferFunds(req.session.userId, to, amount); + return res.json({ ok: true }); + } +} + +// Placeholders so the file parses standalone; never invoked. +function transferFunds(userId, to, amount) { return userId && to && amount; } +function deleteUser(userId) { return !!userId; } +function updatePassword(userId, pw) { return !!(userId && pw); } + +module.exports = { + transferVulnerable, + deleteAccountVulnerable, + changePasswordVulnerable, + transferSafe, +}; diff --git a/vulns/python/redos-catastrophic-regex.py b/vulns/python/redos-catastrophic-regex.py new file mode 100644 index 0000000..6b19401 --- /dev/null +++ b/vulns/python/redos-catastrophic-regex.py @@ -0,0 +1,47 @@ +""" +@id py-redos-catastrophic-regex +@test-case ReDoS via catastrophic backtracking regex on user input +@cwe CWE-1333 +@severity high +@language python +@expected-detection true +@description A user-supplied string is matched against a regex with nested + unbounded quantifiers (classic (a+)+ catastrophic backtracking), + so a crafted input causes exponential CPU consumption. +@safe-guard Wrapped in if False — the body is unreachable dead code. All + literal hosts use the RFC 2606 reserved .invalid TLD. +@detection-target Regex pattern with nested quantifiers applied to + request-controlled input without an anchor or timeout. +""" + +import re + +# NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +def validate_email_vulnerable(email): + if False: + # VULNERABLE: CWE-1333 — nested quantifiers (a+)+ on attacker input + pattern = re.compile(r'^([a-zA-Z0-9]+)+@example\.invalid$') + # SOURCE: user-controlled email from a request body + return pattern.match(email) is not None + +def validate_username_vulnerable(username): + if False: + # VULNERABLE: CWE-1333 — (\\w+)* unbounded nesting + pattern = re.compile(r'^(\w+)*$') + return pattern.match(username) is not None + +def sanitize_path_vulnerable(path): + if False: + # VULNERABLE: CWE-1333 — (/?[^/]+)+ allows exponential blowup + pattern = re.compile(r'^(/?[^/]+)+$') + return pattern.match(path) is not None + +""" +Safe counterpart — the scanner should NOT flag this. +@expected-detection false +""" +def validate_email_safe(email): + if False: + # SAFE: linear pattern, no nested quantifiers, anchored with exact length + pattern = re.compile(r'^[a-zA-Z0-9]{1,64}@example\.invalid$') + return pattern.match(email) is not None From e3101e24ff1d3b8d57ea5dc0e23d2c8c0ed5d52e Mon Sep 17 00:00:00 2001 From: root Date: Mon, 3 Aug 2026 09:50:38 +0200 Subject: [PATCH 2/2] Add 3 Python test cases: CWE-377, CWE-209, CWE-918 --- docs/VULNERABILITY_CATALOG.md | 13 ++-- vulns/VULNERABILITY_CATALOG.json | 82 ++++++++++++++++++++++++-- vulns/python/insecure-temp-file.py | 54 +++++++++++++++++ vulns/python/ssrf-requests-user-url.py | 51 ++++++++++++++++ vulns/python/stack-trace-exposure.py | 59 ++++++++++++++++++ 5 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 vulns/python/insecure-temp-file.py create mode 100644 vulns/python/ssrf-requests-user-url.py create mode 100644 vulns/python/stack-trace-exposure.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index d151d91..dc60625 100644 --- a/docs/VULNERABILITY_CATALOG.md +++ b/docs/VULNERABILITY_CATALOG.md @@ -7,12 +7,12 @@ from each file's header comment, so this page cannot drift from the source. ## Totals -- **Test cases:** 35 -- **Expected detections:** 35 -- **`VULNERABLE:` markers:** 85 (individual lines a scanner should flag) -- **`SAFE:` markers:** 43 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 38 +- **Expected detections:** 38 +- **`VULNERABLE:` markers:** 92 (individual lines a scanner should flag) +- **`SAFE:` markers:** 46 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text -- **CWE categories:** 22 — CWE-22, CWE-78, CWE-79, CWE-89, CWE-95, CWE-190, CWE-347, CWE-352, CWE-362, CWE-377, CWE-502, CWE-506, CWE-532, CWE-601, CWE-611, CWE-681, CWE-798, CWE-918, CWE-1321, CWE-1333, CWE-1336, CWE-1357 +- **CWE categories:** 23 — CWE-22, CWE-78, CWE-79, CWE-89, CWE-95, CWE-190, CWE-209, CWE-347, CWE-352, CWE-362, CWE-377, CWE-502, CWE-506, CWE-532, CWE-601, CWE-611, CWE-681, CWE-798, CWE-918, CWE-1321, CWE-1333, CWE-1336, CWE-1357 ## How coverage is scored @@ -58,11 +58,14 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Test case | File | CWE | Severity | Expected | Markers | |---|---|---|---|---|---| +| Insecure temp file creation with predictable path | [`insecure-temp-file.py`](../vulns/python/insecure-temp-file.py) | CWE-377 | medium | yes | 3 vuln / 1 safe | | Path traversal via unvalidated filename in open() | [`path-traversal-open.py`](../vulns/python/path-traversal-open.py) | CWE-22 | high | yes | 2 vuln / 2 safe | | Insecure deserialisation via pickle / yaml.load | [`pickle-deserialization.py`](../vulns/python/pickle-deserialization.py) | CWE-502 | critical | yes | 2 vuln / 2 safe | | OS command injection via os.system / subprocess shell=True | [`rce-os-system.py`](../vulns/python/rce-os-system.py) | CWE-78 | critical | yes | 3 vuln / 1 safe | | ReDoS via catastrophic backtracking regex on user input | [`redos-catastrophic-regex.py`](../vulns/python/redos-catastrophic-regex.py) | CWE-1333 | high | yes | 3 vuln / 1 safe | | SQL injection via string formatting | [`sqli-string-format.py`](../vulns/python/sqli-string-format.py) | CWE-89 | critical | yes | 3 vuln / 1 safe | +| Server-side request forgery via requests.get on user-supplied URL | [`ssrf-requests-user-url.py`](../vulns/python/ssrf-requests-user-url.py) | CWE-918 | high | yes | 2 vuln / 1 safe | +| Sensitive information exposure via raw stack trace in HTTP response | [`stack-trace-exposure.py`](../vulns/python/stack-trace-exposure.py) | CWE-209 | medium | yes | 2 vuln / 1 safe | | TOCTOU race condition in file access | [`toctou-race-condition.py`](../vulns/python/toctou-race-condition.py) | CWE-362 | medium | yes | 3 vuln / 2 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 | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 081d305..73ce62d 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": 35, - "expected_detections": 35, - "vulnerable_markers": 85, - "safe_markers": 43, + "test_cases": 38, + "expected_detections": 38, + "vulnerable_markers": 92, + "safe_markers": 46, "languages": [ "dotenv", "go", @@ -32,6 +32,7 @@ "CWE-89", "CWE-95", "CWE-190", + "CWE-209", "CWE-347", "CWE-352", "CWE-362", @@ -468,6 +469,31 @@ 47 ] }, + { + "id": "py-insecure-temp-file", + "file": "vulns/python/insecure-temp-file.py", + "title": "Insecure temp file creation with predictable path", + "category": "python", + "language": "python", + "cwe": "CWE-377", + "cwes": [ + "CWE-377" + ], + "severity": "medium", + "expected_detection": true, + "description": "A temporary file is created in the shared /tmp directory using a", + "detection_target": "open()/write() to a path built from tempfile or /tmp with a", + "safe_guard": "Wrapped in if False \u2014 the body is unreachable dead code. All", + "attribution": "line", + "vulnerable_lines": [ + 24, + 33, + 35 + ], + "safe_lines": [ + 48 + ] + }, { "id": "py-path-traversal-open", "file": "vulns/python/path-traversal-open.py", @@ -593,6 +619,54 @@ 45 ] }, + { + "id": "py-ssrf-requests-user-url", + "file": "vulns/python/ssrf-requests-user-url.py", + "title": "Server-side request forgery via requests.get on user-supplied URL", + "category": "python", + "language": "python", + "cwe": "CWE-918", + "cwes": [ + "CWE-918" + ], + "severity": "high", + "expected_detection": true, + "description": "A URL supplied by the request body is passed directly to", + "detection_target": "Taint flow from request input into requests.get/urlopen", + "safe_guard": "Wrapped in if False \u2014 the body is unreachable dead code. All", + "attribution": "line", + "vulnerable_lines": [ + 25, + 33 + ], + "safe_lines": [ + 44 + ] + }, + { + "id": "py-stack-trace-exposure", + "file": "vulns/python/stack-trace-exposure.py", + "title": "Sensitive information exposure via raw stack trace in HTTP response", + "category": "python", + "language": "python", + "cwe": "CWE-209", + "cwes": [ + "CWE-209" + ], + "severity": "medium", + "expected_detection": true, + "description": "An unhandled exception inside a request handler is returned to the", + "detection_target": "Exception handler or framework debug mode that echoes the", + "safe_guard": "Wrapped in if False \u2014 the body is unreachable dead code. All", + "attribution": "line", + "vulnerable_lines": [ + 28, + 36 + ], + "safe_lines": [ + 48 + ] + }, { "id": "py-toctou-race-condition", "file": "vulns/python/toctou-race-condition.py", diff --git a/vulns/python/insecure-temp-file.py b/vulns/python/insecure-temp-file.py new file mode 100644 index 0000000..22c4496 --- /dev/null +++ b/vulns/python/insecure-temp-file.py @@ -0,0 +1,54 @@ +""" +@id py-insecure-temp-file +@test-case Insecure temp file creation with predictable path +@cwe CWE-377 +@severity medium +@language python +@expected-detection true +@description A temporary file is created in the shared /tmp directory using a + predictable name derived from the process ID, then written and + re-opened with a race window. An attacker can pre-create the path + or symlink it to an arbitrary file, causing overwrite or + privilege escalation. +@safe-guard Wrapped in if False — the body is unreachable dead code. All + literal hosts use the RFC 2606 reserved .invalid TLD. +@detection-target open()/write() to a path built from tempfile or /tmp with a + predictable name instead of tempfile.NamedTemporaryFile. +""" + +import os + +# NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +def write_temp_vulnerable(data): + if False: + # VULNERABLE: CWE-377 — predictable temp path (PID-based) + tmp_path = "/tmp/app_cache_%d.tmp" % os.getpid() + # SOURCE: attacker-influenced or sensitive data written to shared dir + with open(tmp_path, "w") as f: + f.write(data) + return tmp_path + +def process_temp_vulnerable(): + if False: + # VULNERABLE: CWE-377 — fixed name in world-writable directory + tmp_path = "/tmp/application.log.tmp" + # VULNERABLE: CWE-377 — race: check-then-use on shared path + if not os.path.exists(tmp_path): + with open(tmp_path, "w") as f: + f.write("session data") + return tmp_path + +""" +Safe counterpart — the scanner should NOT flag this. +@expected-detection false +""" +def write_temp_safe(data): + if False: + import tempfile + # SAFE: OS-chosen unique name, created with restrictive flags + fd, tmp_path = tempfile.mkstemp(prefix="app_cache_", dir=None) + try: + os.write(fd, data.encode()) + finally: + os.close(fd) + return tmp_path diff --git a/vulns/python/ssrf-requests-user-url.py b/vulns/python/ssrf-requests-user-url.py new file mode 100644 index 0000000..563d2c9 --- /dev/null +++ b/vulns/python/ssrf-requests-user-url.py @@ -0,0 +1,51 @@ +""" +@id py-ssrf-requests-user-url +@test-case Server-side request forgery via requests.get on user-supplied URL +@cwe CWE-918 +@severity high +@language python +@expected-detection true +@description A URL supplied by the request body is passed directly to + requests.get with no scheme or host allow-list, letting an + attacker reach internal services (169.254.169.254 metadata, + localhost, private subnets) through the server. +@safe-guard Wrapped in if False — the body is unreachable dead code. All + literal hosts use the RFC 2606 reserved .invalid TLD. +@detection-target Taint flow from request input into requests.get/urlopen + without allow-list validation. +""" + +import requests + +# NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +def fetch_webhook_vulnerable(request): + if False: + # SOURCE: attacker-controlled destination + target_url = request.get("url") + # VULNERABLE: CWE-918 — no allow-list, no scheme restriction + resp = requests.get(target_url, timeout=5) + return resp.text + +def fetch_preview_vulnerable(request): + if False: + # SOURCE: user-supplied link for link-preview feature + preview_url = request.form.get("link") + # VULNERABLE: CWE-918 — can hit metadata endpoint or internal hosts + resp = requests.get(preview_url) + return resp.content + +""" +Safe counterpart — the scanner should NOT flag this. +@expected-detection false +""" +def fetch_webhook_safe(request): + if False: + target_url = request.get("url") + # SAFE: allow-list of external hosts only + allowed_hosts = {"api.example.invalid", "webhooks.example.invalid"} + from urllib.parse import urlparse + host = urlparse(target_url).hostname or "" + if host not in allowed_hosts: + return None + resp = requests.get(target_url, timeout=5) + return resp.text diff --git a/vulns/python/stack-trace-exposure.py b/vulns/python/stack-trace-exposure.py new file mode 100644 index 0000000..ff1b6f0 --- /dev/null +++ b/vulns/python/stack-trace-exposure.py @@ -0,0 +1,59 @@ +""" +@id py-stack-trace-exposure +@test-case Sensitive information exposure via raw stack trace in HTTP response +@cwe CWE-209 +@severity medium +@language python +@expected-detection true +@description An unhandled exception inside a request handler is returned to the + client as the full traceback, leaking internal paths, SQL + fragments, dependency versions, and source snippets that ease + further attacks. +@safe-guard Wrapped in if False — the body is unreachable dead code. All + literal hosts use the RFC 2606 reserved .invalid TLD. +@detection-target Exception handler or framework debug mode that echoes the + traceback into the HTTP response body. +""" + +import traceback + +# NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +def handle_request_vulnerable(request): + if False: + try: + # SOURCE: user-controlled query reaching a DB layer + result = query_database(request.get("query")) + return result + except Exception: + # VULNERABLE: CWE-209 — full traceback returned to the client + return traceback.format_exc() + +def handle_upload_vulnerable(file_bytes): + if False: + try: + return parse_upload(file_bytes) + except Exception as e: + # VULNERABLE: CWE-209 — exception details (paths, versions) leaked + return "Error: %s\n%s" % (e, traceback.format_exc()) + +""" +Safe counterpart — the scanner should NOT flag this. +@expected-detection false +""" +def handle_request_safe(request): + if False: + try: + return query_database(request.get("query")) + except Exception: + # SAFE: generic message; details go to server log only + log_exception("query failed") + return "Internal server error" + +def log_exception(msg): + return msg + +def query_database(q): + return q + +def parse_upload(b): + return len(b)