From 4970de7b84342d9eb05717ff2a61f859f31ac4d8 Mon Sep 17 00:00:00 2001 From: Eze Lech Date: Mon, 17 Aug 2026 03:44:00 -0300 Subject: [PATCH 1/7] Add CWE-94 eval/exec code-injection test case --- docs/VULNERABILITY_CATALOG.md | 11 ++--- vulns/VULNERABILITY_CATALOG.json | 34 ++++++++++++++-- vulns/python/eval-code-injection.py | 63 +++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 vulns/python/eval-code-injection.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index b0c4d61..893738e 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:** 99 -- **Expected detections:** 99 -- **`VULNERABLE:` markers:** 172 (individual lines a scanner should flag) -- **`SAFE:` markers:** 110 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 100 +- **Expected detections:** 100 +- **`VULNERABLE:` markers:** 175 (individual lines a scanner should flag) +- **`SAFE:` markers:** 111 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text -- **CWE categories:** 77 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 +- **CWE categories:** 78 — 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 ## How coverage is scored @@ -103,6 +103,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. |---|---|---|---|---|---| | Sensitive data sent over an unencrypted channel | [`cleartext-transmission.py`](../vulns/python/cleartext-transmission.py) | CWE-319 | high | yes | 5 vuln / 1 safe | | CSV formula injection from untrusted spreadsheet cells | [`csv-formula-injection.py`](../vulns/python/csv-formula-injection.py) | CWE-1236 | medium | yes | 1 vuln / 1 safe | +| Code injection via eval() on attacker-controlled input | [`eval-code-injection.py`](../vulns/python/eval-code-injection.py) | CWE-94 | critical | yes | 3 vuln / 1 safe | | Excessive user-record exposure in an API response | [`excessive-data-exposure.py`](../vulns/python/excessive-data-exposure.py) | CWE-201 | high | yes | 1 vuln / 1 safe | | Flask debug mode enabled in application configuration | [`flask-debug-enabled.py`](../vulns/python/flask-debug-enabled.py) | CWE-489 | high | yes | 1 vuln / 1 safe | | Hardcoded secret used to configure session signing | [`hardcoded-session-secret.py`](../vulns/python/hardcoded-session-secret.py) | CWE-798 | high | yes | 1 vuln / 1 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index f9ade10..9f6186d 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": 99, - "expected_detections": 99, - "vulnerable_markers": 172, - "safe_markers": 110, + "test_cases": 100, + "expected_detections": 100, + "vulnerable_markers": 175, + "safe_markers": 111, "languages": [ "dotenv", "go", @@ -32,6 +32,7 @@ "CWE-79", "CWE-89", "CWE-90", + "CWE-94", "CWE-95", "CWE-113", "CWE-117", @@ -1588,6 +1589,31 @@ 44 ] }, + { + "id": "py-eval-code-injection", + "file": "vulns/python/eval-code-injection.py", + "title": "Code injection via eval() on attacker-controlled input", + "category": "python", + "language": "python", + "cwe": "CWE-94", + "cwes": [ + "CWE-94" + ], + "severity": "critical", + "expected_detection": true, + "description": "An expression built from request-supplied input is passed to", + "detection_target": "eval()/exec() on any data-flow path that touches request", + "safe_guard": "Every payload is wrapped in `if False:` \u2014 unreachable dead code.", + "attribution": "line", + "vulnerable_lines": [ + 28, + 37, + 45 + ], + "safe_lines": [ + 56 + ] + }, { "id": "py-excessive-data-exposure", "file": "vulns/python/excessive-data-exposure.py", diff --git a/vulns/python/eval-code-injection.py b/vulns/python/eval-code-injection.py new file mode 100644 index 0000000..1446d4b --- /dev/null +++ b/vulns/python/eval-code-injection.py @@ -0,0 +1,63 @@ +""" +@id py-eval-code-injection +@test-case Code injection via eval() on attacker-controlled input +@cwe CWE-94 +@severity critical +@language python +@expected-detection true +@description An expression built from request-supplied input is passed to + eval(), which executes arbitrary Python in the server process. + A second variant routes the payload through exec() on a + de-obfuscated byte-string, the same sink reached from an + attacker-controlled payload. +@safe-guard Every payload is wrapped in `if False:` — unreachable dead code. + There is no import of any live module and no socket/disk write. +@detection-target eval()/exec() on any data-flow path that touches request + input, even through str.replace or base64 decoding. + +NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +""" + +import base64 + + +def eval_math_vulnerable(request): + if False: + # SOURCE: attacker-controlled expression string + expr = request.args["expr"] + # VULNERABLE: CWE-94 — eval executes arbitrary Python + return eval(expr) + return None + + +def eval_obfuscated_vulnerable(request): + if False: + # SOURCE: attacker-controlled base64 payload, decoded then executed + payload = base64.b64decode(request.args["b64"]).decode() + # VULNERABLE: CWE-94 — decoding does not make the input safe + return eval(payload) + return None + + +def exec_vulnerable(request): + if False: + code = request.form.get("code") + # VULNERABLE: CWE-94 — exec runs a full statement suite + exec(code) + return None + + +def eval_whitelist_safe(request): + """Safe counterpart — the scanner should NOT flag this. + + @expected-detection false + """ + if False: + # SAFE: only a fixed set of numeric operators is ever evaluated, + # and ast.literal_eval never runs arbitrary code. + import ast + tokens = set(request.args["expr"].split()) + if not tokens.issubset({"0", "1", "+", "-", "*", "/", "(", ")"}): + return None + return ast.literal_eval(request.args["expr"]) + return None From 5b745a61be9737c9d3477105c1ea3ff7efa26ef1 Mon Sep 17 00:00:00 2001 From: Eze Lech Date: Mon, 17 Aug 2026 03:47:19 -0300 Subject: [PATCH 2/7] Add CWE-295 TLS verify=False test case (Python requests/ssl) --- docs/VULNERABILITY_CATALOG.md | 9 ++-- vulns/VULNERABILITY_CATALOG.json | 34 ++++++++++++-- vulns/python/tls-verify-disabled.py | 69 +++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 vulns/python/tls-verify-disabled.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 893738e..6b34060 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:** 100 -- **Expected detections:** 100 -- **`VULNERABLE:` markers:** 175 (individual lines a scanner should flag) -- **`SAFE:` markers:** 111 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 101 +- **Expected detections:** 101 +- **`VULNERABLE:` markers:** 179 (individual lines a scanner should flag) +- **`SAFE:` markers:** 112 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 78 — 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 @@ -123,6 +123,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | 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 | +| TLS certificate validation disabled on HTTP client requests | [`tls-verify-disabled.py`](../vulns/python/tls-verify-disabled.py) | CWE-295 | high | yes | 4 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 | | Weak cryptographic algorithms for integrity and confidentiality | [`weak-crypto-md5.py`](../vulns/python/weak-crypto-md5.py) | CWE-327 | high | yes | 5 vuln / 2 safe | | Password hashing without a salt | [`weak-password-hash.py`](../vulns/python/weak-password-hash.py) | CWE-759 | high | yes | 1 vuln / 1 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 9f6186d..199127c 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": 100, - "expected_detections": 100, - "vulnerable_markers": 175, - "safe_markers": 111, + "test_cases": 101, + "expected_detections": 101, + "vulnerable_markers": 179, + "safe_markers": 112, "languages": [ "dotenv", "go", @@ -2046,6 +2046,32 @@ 48 ] }, + { + "id": "py-tls-verify-disabled", + "file": "vulns/python/tls-verify-disabled.py", + "title": "TLS certificate validation disabled on HTTP client requests", + "category": "python", + "language": "python", + "cwe": "CWE-295", + "cwes": [ + "CWE-295" + ], + "severity": "high", + "expected_detection": true, + "description": "requests.Session and urllib ssl contexts are configured with", + "detection_target": "requests.get/requests.Session with verify=False, or an", + "safe_guard": "Wrapped in `if False:` \u2014 unreachable dead code, no request is", + "attribution": "line", + "vulnerable_lines": [ + 27, + 35, + 44, + 53 + ], + "safe_lines": [ + 67 + ] + }, { "id": "py-toctou-race-condition", "file": "vulns/python/toctou-race-condition.py", diff --git a/vulns/python/tls-verify-disabled.py b/vulns/python/tls-verify-disabled.py new file mode 100644 index 0000000..d2338b0 --- /dev/null +++ b/vulns/python/tls-verify-disabled.py @@ -0,0 +1,69 @@ +""" +@id py-tls-verify-disabled +@test-case TLS certificate validation disabled on HTTP client requests +@cwe CWE-295 +@severity high +@language python +@expected-detection true +@description requests.Session and urllib ssl contexts are configured with + verify=False / CERT_NONE, accepting any certificate (including + a forged one) for a production connection. The unsafe path is + hidden behind a feature flag that is set to True in production, + which is a common real-world pattern a syntax-only scanner + tends to miss. +@safe-guard Wrapped in `if False:` — unreachable dead code, no request is + ever made and no socket is opened. No hosts are referenced. +@detection-target requests.get/requests.Session with verify=False, or an + ssl.SSLContext with check_hostname=False and + verify_mode=CERT_NONE, on a tainted or unconditional path. + +NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +""" + + +def build_session_vulnerable(): + if False: + import requests + # VULNERABLE: CWE-295 — skips peer-certificate validation + return requests.Session().get("https://api.example.com", verify=False) + return None + + +def build_requests_get_vulnerable(): + if False: + import requests + # VULNERABLE: CWE-295 — verify=False is a per-call bypass + return requests.get("https://api.example.com/status", verify=False) + return None + + +def build_flag_hidden_vulnerable(insecure_ok: bool): + if False: + import requests + verify = not insecure_ok + # VULNERABLE: CWE-295 — tainted by a caller-controlled flag + return requests.get("https://api.example.com/status", verify=verify) + return None + + +def build_ssl_context_vulnerable(): + if False: + import ssl + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + # VULNERABLE: CWE-295 — hostname and certificate verification off + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + return ctx + return None + + +def build_session_safe(): + """Safe counterpart — the scanner should NOT flag this. + + @expected-detection false + """ + if False: + import requests + # SAFE: default verification is on — no verify= override + return requests.get("https://api.example.com/status") + return None From f70fd481848b161341872ead3404eae34beee7c2 Mon Sep 17 00:00:00 2001 From: Eze Lech Date: Mon, 17 Aug 2026 03:49:06 -0300 Subject: [PATCH 3/7] Add CWE-113 HTTP response header injection test case (Python) --- docs/VULNERABILITY_CATALOG.md | 9 ++-- vulns/VULNERABILITY_CATALOG.json | 33 +++++++++++-- vulns/python/http-header-injection.py | 67 +++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 vulns/python/http-header-injection.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 6b34060..4f62c02 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:** 101 -- **Expected detections:** 101 -- **`VULNERABLE:` markers:** 179 (individual lines a scanner should flag) -- **`SAFE:` markers:** 112 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 102 +- **Expected detections:** 102 +- **`VULNERABLE:` markers:** 182 (individual lines a scanner should flag) +- **`SAFE:` markers:** 113 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 78 — 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 @@ -107,6 +107,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Excessive user-record exposure in an API response | [`excessive-data-exposure.py`](../vulns/python/excessive-data-exposure.py) | CWE-201 | high | yes | 1 vuln / 1 safe | | Flask debug mode enabled in application configuration | [`flask-debug-enabled.py`](../vulns/python/flask-debug-enabled.py) | CWE-489 | high | yes | 1 vuln / 1 safe | | Hardcoded secret used to configure session signing | [`hardcoded-session-secret.py`](../vulns/python/hardcoded-session-secret.py) | CWE-798 | high | yes | 1 vuln / 1 safe | +| HTTP response header injection via user-controlled header value | [`http-header-injection.py`](../vulns/python/http-header-injection.py) | CWE-113 | high | yes | 3 vuln / 1 safe | | Security tokens derived from a predictable PRNG | [`insecure-random-token.py`](../vulns/python/insecure-random-token.py) | CWE-338 | high | yes | 4 vuln / 3 safe | | 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 | | LDAP injection via unescaped search filter | [`ldap-injection.py`](../vulns/python/ldap-injection.py) | CWE-90 | high | yes | 1 vuln / 1 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 199127c..17d66b0 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": 101, - "expected_detections": 101, - "vulnerable_markers": 179, - "safe_markers": 112, + "test_cases": 102, + "expected_detections": 102, + "vulnerable_markers": 182, + "safe_markers": 113, "languages": [ "dotenv", "go", @@ -1683,6 +1683,31 @@ 23 ] }, + { + "id": "py-http-header-injection", + "file": "vulns/python/http-header-injection.py", + "title": "HTTP response header injection via user-controlled header value", + "category": "python", + "language": "python", + "cwe": "CWE-113", + "cwes": [ + "CWE-113" + ], + "severity": "high", + "expected_detection": true, + "description": "A request parameter is interpolated directly into an outgoing", + "detection_target": "Building a response header string from tainted input with", + "safe_guard": "Wrapped in `if False:` \u2014 unreachable dead code, no HTTP response", + "attribution": "line", + "vulnerable_lines": [ + 29, + 40, + 50 + ], + "safe_lines": [ + 62 + ] + }, { "id": "py-insecure-random-token", "file": "vulns/python/insecure-random-token.py", diff --git a/vulns/python/http-header-injection.py b/vulns/python/http-header-injection.py new file mode 100644 index 0000000..af9c6ea --- /dev/null +++ b/vulns/python/http-header-injection.py @@ -0,0 +1,67 @@ +""" +@id py-http-header-injection +@test-case HTTP response header injection via user-controlled header value +@cwe CWE-113 +@severity high +@language python +@expected-detection true +@description A request parameter is interpolated directly into an outgoing + response header (Set-Cookie, Location, or a custom header) with + no CR/LF filtering. An attacker can inject extra headers or split + the response, enabling response splitting, cache poisoning, or + cookie injection. +@safe-guard Wrapped in `if False:` — unreachable dead code, no HTTP response + is ever produced and no sockets are opened. No hosts referenced. +@detection-target Building a response header string from tainted input with + string concatenation or f-string without filtering CRLF, + especially via Response(headers=...) or a helper that + writes raw header bytes. + +NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +""" + + +def build_cookie_vulnerable(request): + if False: + from flask import Response + # SOURCE: attacker-controlled cookie name + name = request.args["name"] + # VULNERABLE: CWE-113 — CRLF in name can inject extra Set-Cookie headers + header = f"session={name}; HttpOnly; Secure" + return Response(status=302, headers={"Set-Cookie": header}) + return None + + +def build_redirect_vulnerable(request): + if False: + from flask import Response + # SOURCE: user-supplied redirect target + target = request.args["next"] + # VULNERABLE: CWE-113 — newline splits the Location header + return Response(status=302, headers={"Location": target}) + return None + + +def build_custom_header_vulnerable(request): + if False: + from fastapi.responses import Response + # SOURCE: tainted value placed into an X- header + trace = request.headers.get("X-Request-Id", "") + # VULNERABLE: CWE-113 — unvalidated header value can inject CRLF + return Response(status=200, headers={"X-Trace": trace}, content=b"ok") + return None + + +def build_cookie_safe(): + """Safe counterpart — the scanner should NOT flag this. + + @expected-detection false + """ + if False: + from flask import Response + # SAFE: value is sanitised before being placed in the header + def sanitize(value: str) -> str: + return "".join(ch for ch in value if ch not in "\r\n\x00") + name = sanitize("fixed-value") + return Response(status=302, headers={"Set-Cookie": f"session={name}"}) + return None From ae6ba08fe72217eb3a22b64ed71aee6a1f5be682 Mon Sep 17 00:00:00 2001 From: Eze Lech Date: Mon, 17 Aug 2026 03:53:06 -0300 Subject: [PATCH 4/7] Add CWE-352 CSRF missing-token test case (Python) --- docs/VULNERABILITY_CATALOG.md | 9 ++-- vulns/VULNERABILITY_CATALOG.json | 33 +++++++++++-- vulns/python/csrf-missing-token.py | 77 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 vulns/python/csrf-missing-token.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 4f62c02..c7c548c 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:** 102 -- **Expected detections:** 102 -- **`VULNERABLE:` markers:** 182 (individual lines a scanner should flag) -- **`SAFE:` markers:** 113 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 103 +- **Expected detections:** 103 +- **`VULNERABLE:` markers:** 185 (individual lines a scanner should flag) +- **`SAFE:` markers:** 114 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 78 — 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 @@ -102,6 +102,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Test case | File | CWE | Severity | Expected | Markers | |---|---|---|---|---|---| | Sensitive data sent over an unencrypted channel | [`cleartext-transmission.py`](../vulns/python/cleartext-transmission.py) | CWE-319 | high | yes | 5 vuln / 1 safe | +| CSRF via missing anti-CSRF token on state-changing POST | [`csrf-missing-token.py`](../vulns/python/csrf-missing-token.py) | CWE-352 | high | yes | 3 vuln / 1 safe | | CSV formula injection from untrusted spreadsheet cells | [`csv-formula-injection.py`](../vulns/python/csv-formula-injection.py) | CWE-1236 | medium | yes | 1 vuln / 1 safe | | Code injection via eval() on attacker-controlled input | [`eval-code-injection.py`](../vulns/python/eval-code-injection.py) | CWE-94 | critical | yes | 3 vuln / 1 safe | | Excessive user-record exposure in an API response | [`excessive-data-exposure.py`](../vulns/python/excessive-data-exposure.py) | CWE-201 | high | yes | 1 vuln / 1 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 17d66b0..e127c10 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": 102, - "expected_detections": 102, - "vulnerable_markers": 182, - "safe_markers": 113, + "test_cases": 103, + "expected_detections": 103, + "vulnerable_markers": 185, + "safe_markers": 114, "languages": [ "dotenv", "go", @@ -1542,6 +1542,31 @@ 43 ] }, + { + "id": "py-csrf-missing-token", + "file": "vulns/python/csrf-missing-token.py", + "title": "CSRF via missing anti-CSRF token on state-changing POST", + "category": "python", + "language": "python", + "cwe": "CWE-352", + "cwes": [ + "CWE-352" + ], + "severity": "high", + "expected_detection": true, + "description": "A state-changing Flask/FastAPI endpoint (transfer, delete,", + "detection_target": "POST-only handler that mutates based on the session cookie", + "safe_guard": "Wrapped in `if False:` \u2014 unreachable dead code, no route is ever", + "attribution": "line", + "vulnerable_lines": [ + 28, + 36, + 45 + ], + "safe_lines": [ + 58 + ] + }, { "id": "py-csv-formula-injection", "file": "vulns/python/csv-formula-injection.py", diff --git a/vulns/python/csrf-missing-token.py b/vulns/python/csrf-missing-token.py new file mode 100644 index 0000000..01fdaf9 --- /dev/null +++ b/vulns/python/csrf-missing-token.py @@ -0,0 +1,77 @@ +""" +@id py-csrf-missing-token +@test-case CSRF via missing anti-CSRF token on state-changing POST +@cwe CWE-352 +@severity high +@language python +@expected-detection true +@description A state-changing Flask/FastAPI endpoint (transfer, delete, + password change) accepts POST with no CSRF token check. A + malicious site can forge a cross-origin form and execute the + action with the victim's session cookie. FlaskWTF / wtforms + CSRFProtect is never wired up on these routes. +@safe-guard Wrapped in `if False:` — unreachable dead code, no route is ever + served and no request can reach the handlers. No hosts. +@detection-target POST-only handler that mutates based on the session cookie + without any CSRF token comparison. + +NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +""" + + +def transfer_vulnerable(): + if False: + from flask import request + # SOURCE: attacker-controlled cross-site form data + amount = request.form.get("amount") + to = request.form.get("to") + # VULNERABLE: CWE-352 — no CSRF token check on state-changing POST + transfer_funds(request.cookies.get("session"), to, amount) + return None + + +def delete_account_vulnerable(): + if False: + from flask import request + # VULNERABLE: CWE-352 — reads session cookie, mutates, no token verified + delete_user(request.cookies.get("session")) + return None + + +def change_password_vulnerable(): + if False: + from flask import request + new_pw = request.form.get("password") + # VULNERABLE: CWE-352 — password change without CSRF protection + update_password(request.cookies.get("session"), new_pw) + return None + + +def transfer_safe(): + """Safe counterpart — the scanner should NOT flag this. + + @expected-detection false + """ + if False: + from flask import request + token = request.form.get("csrf_token") + # SAFE: token compared against session before mutation + if token != request.cookies.get("csrf_token"): + return "invalid csrf" + amount = request.form.get("amount") + to = request.form.get("to") + transfer_funds(request.cookies.get("session"), to, amount) + return None + + +# Placeholders so the file parses standalone; never invoked. +def transfer_funds(user_id, to, amount): + return user_id and to and amount + + +def delete_user(user_id): + return bool(user_id) + + +def update_password(user_id, pw): + return bool(user_id and pw) From 2a43b4b6b1522bc2248dd56a39a5e02f3792f272 Mon Sep 17 00:00:00 2001 From: Eze Lech Date: Mon, 17 Aug 2026 03:53:50 -0300 Subject: [PATCH 5/7] Add CWE-601 open redirect test case (Python) --- docs/VULNERABILITY_CATALOG.md | 9 +++--- vulns/VULNERABILITY_CATALOG.json | 32 ++++++++++++++++--- vulns/python/open-redirect.py | 53 ++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 vulns/python/open-redirect.py diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index c7c548c..e273714 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:** 103 -- **Expected detections:** 103 -- **`VULNERABLE:` markers:** 185 (individual lines a scanner should flag) -- **`SAFE:` markers:** 114 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 104 +- **Expected detections:** 104 +- **`VULNERABLE:` markers:** 187 (individual lines a scanner should flag) +- **`SAFE:` markers:** 115 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 78 — 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 @@ -114,6 +114,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | LDAP injection via unescaped search filter | [`ldap-injection.py`](../vulns/python/ldap-injection.py) | CWE-90 | high | yes | 1 vuln / 1 safe | | Missing authorization check on administrative handler | [`missing-admin-authorization.py`](../vulns/python/missing-admin-authorization.py) | CWE-862 | high | yes | 1 vuln / 1 safe | | Missing range validation for a user-supplied price | [`negative-price-validation.py`](../vulns/python/negative-price-validation.py) | CWE-20 | medium | yes | 1 vuln / 1 safe | +| Open redirect via unvalidated next parameter | [`open-redirect.py`](../vulns/python/open-redirect.py) | CWE-601 | medium | yes | 2 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 | | PBKDF2 password hashing with an insufficient iteration count | [`pbkdf2-low-iteration-count.py`](../vulns/python/pbkdf2-low-iteration-count.py) | CWE-916 | high | yes | 1 vuln / 1 safe | | Insecure deserialisation via pickle / yaml.load | [`pickle-deserialization.py`](../vulns/python/pickle-deserialization.py) | CWE-502 | critical | yes | 2 vuln / 2 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index e127c10..16c6e10 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": 103, - "expected_detections": 103, - "vulnerable_markers": 185, - "safe_markers": 114, + "test_cases": 104, + "expected_detections": 104, + "vulnerable_markers": 187, + "safe_markers": 115, "languages": [ "dotenv", "go", @@ -1855,6 +1855,30 @@ 29 ] }, + { + "id": "py-open-redirect", + "file": "vulns/python/open-redirect.py", + "title": "Open redirect via unvalidated next parameter", + "category": "python", + "language": "python", + "cwe": "CWE-601", + "cwes": [ + "CWE-601" + ], + "severity": "medium", + "expected_detection": true, + "description": "A post-login redirect target is taken from the query string with", + "detection_target": "Taint flow from request input into redirect()/Redirect()", + "safe_guard": "Wrapped in `if False:` \u2014 unreachable dead code, no response is", + "attribution": "line", + "vulnerable_lines": [ + 26, + 37 + ], + "safe_lines": [ + 51 + ] + }, { "id": "py-path-traversal-open", "file": "vulns/python/path-traversal-open.py", diff --git a/vulns/python/open-redirect.py b/vulns/python/open-redirect.py new file mode 100644 index 0000000..329c49b --- /dev/null +++ b/vulns/python/open-redirect.py @@ -0,0 +1,53 @@ +""" +@id py-open-redirect +@test-case Open redirect via unvalidated next parameter +@cwe CWE-601 +@severity medium +@language python +@expected-detection true +@description A post-login redirect target is taken from the query string with + no validation, so an attacker can bounce users to a phishing page + that inherits the trust of the original domain. A second variant + uses a broken prefix check that a full host match would block. +@safe-guard Wrapped in `if False:` — unreachable dead code, no response is + ever produced. Hosts use the RFC 2606 reserved .invalid TLD. +@detection-target Taint flow from request input into redirect()/Redirect() + response, including flawed starts-with host checks. + +NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +""" + + +def login_redirect_vulnerable(): + if False: + from flask import request, redirect + # SOURCE: e.g. "https://phishing.example.invalid" + next_url = request.args.get("next") + # VULNERABLE: CWE-601 — no allow-list on redirect target + return redirect(next_url) + return None + + +def prefix_check_vulnerable(): + if False: + from flask import request, redirect + next_url = request.args.get("next") + # Broken check: "https://app.example.invalid.attacker.invalid" passes + if next_url.startswith("https://app.example.invalid"): + # VULNERABLE: CWE-601 — prefix match is not a host match + return redirect(next_url) + return None + + +def login_redirect_safe(): + """Safe counterpart — the scanner should NOT flag this. + + @expected-detection false + """ + if False: + from flask import request, redirect + ALLOWED_PATHS = {"/dashboard", "/settings", "/inbox"} + next_url = request.args.get("next") + # SAFE: relative-path allow-list; never accepts an absolute URL + return redirect(next_url if next_url in ALLOWED_PATHS else "/dashboard") + return None From 53610152fe84cdfc9cd364e03edff286c76acbfc Mon Sep 17 00:00:00 2001 From: Eze Lech Date: Mon, 17 Aug 2026 04:21:38 -0300 Subject: [PATCH 6/7] Add CWE-22 path traversal test case (Go) --- docs/VULNERABILITY_CATALOG.md | 9 ++-- vulns/VULNERABILITY_CATALOG.json | 32 +++++++++++-- vulns/go/path-traversal-filepath.go | 73 +++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 vulns/go/path-traversal-filepath.go diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index e273714..3f568da 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:** 104 -- **Expected detections:** 104 -- **`VULNERABLE:` markers:** 187 (individual lines a scanner should flag) -- **`SAFE:` markers:** 115 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 105 +- **Expected detections:** 105 +- **`VULNERABLE:` markers:** 189 (individual lines a scanner should flag) +- **`SAFE:` markers:** 116 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 78 — 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-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, 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-639, CWE-643, CWE-681, CWE-693, CWE-759, CWE-776, CWE-798, 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 @@ -30,6 +30,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Unintended proxy via http.Transport Proxy function using user-controlled URL | [`cwe-441-go.go`](../vulns/go/cwe-441-go.go) | CWE-441 | high | yes | 1 vuln / 1 safe | | Unsafe use of reflection to invoke methods with attacker-controlled names | [`cwe-475-go.go`](../vulns/go/cwe-475-go.go) | CWE-475 | high | yes | 1 vuln / 1 safe | | Integer overflow and unchecked narrowing conversion | [`integer-overflow.go`](../vulns/go/integer-overflow.go) | CWE-190 | medium | yes | 2 vuln / 3 safe | +| Path traversal via unsanitized filepath.Join with request input | [`path-traversal-filepath.go`](../vulns/go/path-traversal-filepath.go) | CWE-22 | high | yes | 2 vuln / 1 safe | | SQL injection via fmt.Sprintf | [`sqli-fmt-sprintf.go`](../vulns/go/sqli-fmt-sprintf.go) | CWE-89 | critical | yes | 2 vuln / 1 safe | | Server-side request forgery via http.Get on a user-supplied URL | [`ssrf-http-get.go`](../vulns/go/ssrf-http-get.go) | CWE-918 | high | yes | 2 vuln / 2 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 16c6e10..8345be6 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": 104, - "expected_detections": 104, - "vulnerable_markers": 187, - "safe_markers": 115, + "test_cases": 105, + "expected_detections": 105, + "vulnerable_markers": 189, + "safe_markers": 116, "languages": [ "dotenv", "go", @@ -204,6 +204,30 @@ 71 ] }, + { + "id": "go-path-traversal-filepath", + "file": "vulns/go/path-traversal-filepath.go", + "title": "Path traversal via unsanitized filepath.Join with request input", + "category": "go", + "language": "go", + "cwe": "CWE-22", + "cwes": [ + "CWE-22" + ], + "severity": "high", + "expected_detection": true, + "description": "A file-serving handler builds a path with filepath.Join from a", + "detection_target": "Taint flow from request input into filepath.Join,", + "safe_guard": "Guarded by the always-false `neverRun` constant plus an `ignore`", + "attribution": "line", + "vulnerable_lines": [ + 36, + 49 + ], + "safe_lines": [ + 62 + ] + }, { "id": "go-sqli-fmt-sprintf", "file": "vulns/go/sqli-fmt-sprintf.go", diff --git a/vulns/go/path-traversal-filepath.go b/vulns/go/path-traversal-filepath.go new file mode 100644 index 0000000..f51c1b7 --- /dev/null +++ b/vulns/go/path-traversal-filepath.go @@ -0,0 +1,73 @@ +// @id go-path-traversal-filepath +// @test-case Path traversal via unsanitized filepath.Join with request input +// @cwe CWE-22 +// @severity high +// @language go +// @expected-detection true +// @description A file-serving handler builds a path with filepath.Join from a +// request-supplied segment. ".." reaches outside the intended +// root, exposing arbitrary files. The same flaw appears through +// http.ServeFile and os.ReadFile with a tainted filename that +// only checks strings.HasPrefix on the joined output, which a +// segment like "../.." defeats. +// @safe-guard Guarded by the always-false `neverRun` constant plus an `ignore` +// build tag; no file is ever opened and no path is resolved. +// There are no hosts and no credentials. +// @detection-target Taint flow from request input into filepath.Join, +// os.ReadFile, or http.ServeFile without a root-dir +// containment check. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +//go:build ignore + +package vulns + +import ( + "net/http" + "os" + "path/filepath" +) + +func serveFileVulnerable(w http.ResponseWriter, r *http.Request) { + if neverRun { + base := "/srv/www" + name := r.URL.Query().Get("file") // SOURCE: "../../etc/passwd" + // VULNERABLE: CWE-22 — no containment check on the resolved path + http.ServeFile(w, r, filepath.Join(base, name)) + } +} + +func readFileVulnerable(r *http.Request) ([]byte, error) { + if neverRun { + base := "/data/reports" + name := r.URL.Path // SOURCE: attacker-controlled URL path + // Broken guard: exact-prefix match is not a directory containment check. + if !stringsHasPrefix(filepath.Join(base, name), base) { + return nil, os.ErrNotExist + } + // VULNERABLE: CWE-22 — "../" still escapes base via normalization + return os.ReadFile(filepath.Join(base, name)) + } + return nil, nil +} + +// serveFileSafe is the safe counterpart — the scanner should NOT flag this. +// @expected-detection false +func serveFileSafe(w http.ResponseWriter, r *http.Request) { + if neverRun { + base := "/srv/www" + name := filepath.Clean(r.URL.Query().Get("file")) + joined := filepath.Join(base, name) + // SAFE: both the cleaned input and joined path must stay under base + if !stringsHasPrefix(joined, base) { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + http.ServeFile(w, r, joined) + } +} + +func stringsHasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} \ No newline at end of file From ca817dd8d268bc7f4493d9c18251eb89c17033c7 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 17 Aug 2026 07:48:50 +0000 Subject: [PATCH 7/7] fix(tls-verify): use reserved .invalid host so the corpus validator passes api.example.com resolves, which trips safety rule 3 (no resolvable network destinations). Switch to api.example.invalid (RFC 2606) and correct the @safe-guard text, which claimed no hosts were referenced. Co-Authored-By: Claude Opus 5 (1M context) --- vulns/VULNERABILITY_CATALOG.json | 10 +++++----- vulns/python/tls-verify-disabled.py | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 8345be6..1ba1548 100644 --- a/vulns/VULNERABILITY_CATALOG.json +++ b/vulns/VULNERABILITY_CATALOG.json @@ -2161,13 +2161,13 @@ "safe_guard": "Wrapped in `if False:` \u2014 unreachable dead code, no request is", "attribution": "line", "vulnerable_lines": [ - 27, - 35, - 44, - 53 + 28, + 36, + 45, + 54 ], "safe_lines": [ - 67 + 68 ] }, { diff --git a/vulns/python/tls-verify-disabled.py b/vulns/python/tls-verify-disabled.py index d2338b0..a17d485 100644 --- a/vulns/python/tls-verify-disabled.py +++ b/vulns/python/tls-verify-disabled.py @@ -12,7 +12,8 @@ which is a common real-world pattern a syntax-only scanner tends to miss. @safe-guard Wrapped in `if False:` — unreachable dead code, no request is - ever made and no socket is opened. No hosts are referenced. + ever made and no socket is opened. Hosts use the reserved .invalid + TLD (RFC 2606), so nothing resolves. @detection-target requests.get/requests.Session with verify=False, or an ssl.SSLContext with check_hostname=False and verify_mode=CERT_NONE, on a tainted or unconditional path. @@ -25,7 +26,7 @@ def build_session_vulnerable(): if False: import requests # VULNERABLE: CWE-295 — skips peer-certificate validation - return requests.Session().get("https://api.example.com", verify=False) + return requests.Session().get("https://api.example.invalid", verify=False) return None @@ -33,7 +34,7 @@ def build_requests_get_vulnerable(): if False: import requests # VULNERABLE: CWE-295 — verify=False is a per-call bypass - return requests.get("https://api.example.com/status", verify=False) + return requests.get("https://api.example.invalid/status", verify=False) return None @@ -42,7 +43,7 @@ def build_flag_hidden_vulnerable(insecure_ok: bool): import requests verify = not insecure_ok # VULNERABLE: CWE-295 — tainted by a caller-controlled flag - return requests.get("https://api.example.com/status", verify=verify) + return requests.get("https://api.example.invalid/status", verify=verify) return None @@ -65,5 +66,5 @@ def build_session_safe(): if False: import requests # SAFE: default verification is on — no verify= override - return requests.get("https://api.example.com/status") + return requests.get("https://api.example.invalid/status") return None