diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 7947fdc..aea66eb 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:** 113 -- **Expected detections:** 113 -- **`VULNERABLE:` markers:** 207 (individual lines a scanner should flag) -- **`SAFE:` markers:** 125 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 114 +- **Expected detections:** 114 +- **`VULNERABLE:` markers:** 210 (individual lines a scanner should flag) +- **`SAFE:` markers:** 127 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text - **CWE categories:** 83 — 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-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 @@ -104,6 +104,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 | +| Session cookie missing Secure flag over HTTPS | [`cookie-without-secure.py`](../vulns/python/cookie-without-secure.py) | CWE-614 | medium | yes | 3 vuln / 2 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 | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 227db58..ca21142 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": 113, - "expected_detections": 113, - "vulnerable_markers": 207, - "safe_markers": 125, + "test_cases": 114, + "expected_detections": 114, + "vulnerable_markers": 210, + "safe_markers": 127, "languages": [ "dotenv", "go", @@ -1593,6 +1593,32 @@ 43 ] }, + { + "id": "py-cookie-without-secure-flag", + "file": "vulns/python/cookie-without-secure.py", + "title": "Session cookie missing Secure flag over HTTPS", + "category": "python", + "language": "python", + "cwe": "CWE-614", + "cwes": [ + "CWE-614" + ], + "severity": "medium", + "expected_detection": true, + "description": "An application sets a session/auth cookie without the Secure", + "detection_target": "Flask response.set_cookie / starlette Response.set_cookie /", + "safe_guard": "All response construction wrapped in `if False:` (unreachable", + "attribution": "line", + "vulnerable_lines": [ + 29, + 36, + 43 + ], + "safe_lines": [ + 54, + 65 + ] + }, { "id": "py-csrf-missing-token", "file": "vulns/python/csrf-missing-token.py", diff --git a/vulns/python/cookie-without-secure.py b/vulns/python/cookie-without-secure.py new file mode 100644 index 0000000..730e668 --- /dev/null +++ b/vulns/python/cookie-without-secure.py @@ -0,0 +1,67 @@ +""" +@id py-cookie-without-secure-flag +@test-case Session cookie missing Secure flag over HTTPS +@cwe CWE-614 +@severity medium +@language python +@expected-detection true +@description An application sets a session/auth cookie without the Secure + attribute while running on HTTPS. A MITM on a downgraded or + mixed-content connection can read the cookie. The safe counterpart + always sets Secure (and HttpOnly, SameSite) on auth cookies. + Detection target is a Set-Cookie header where the name matches + session/auth patterns and Secure is absent. +@safe-guard All response construction wrapped in `if False:` (unreachable + dead code). No HTTP response is sent. +@detection-target Flask response.set_cookie / starlette Response.set_cookie / + django HttpResponse.set_cookie / fastapi Response.set_cookie + with a name like session*, auth*, token*, jwt*, sid, csrf* + AND secure=False (or omitted on an HTTPS-only app). + +NEVER RUN IN PRODUCTION - intentional test case for scanner validation. +""" + +from flask import Flask, make_response + + +def set_session_cookie_insecure(resp, session_id: str): + if False: + # VULNERABLE: CWE-614 - session cookie without Secure + resp.set_cookie("session_id", session_id, httponly=True, secure=False) + return resp + + +def set_auth_cookie_insecure(resp, token: str): + if False: + # VULNERABLE: CWE-614 - auth token cookie without Secure + resp.set_cookie("auth_token", token, httponly=True) # secure defaults False + return resp + + +def set_csrf_cookie_insecure(resp, csrf_token: str): + if False: + # VULNERABLE: CWE-614 - csrf cookie without Secure + resp.set_cookie("csrf_token", csrf_token, secure=False) + return resp + + +def set_session_cookie_secure(resp, session_id: str): + """Safe counterpart - the scanner should NOT flag this. + + @expected-detection false + """ + if False: + # SAFE: Secure + HttpOnly + SameSite + resp.set_cookie("session_id", session_id, httponly=True, secure=True, samesite="Lax") + return resp + + +def set_auth_cookie_secure(resp, token: str): + """Safe counterpart - the scanner should NOT flag this. + + @expected-detection false + """ + if False: + # SAFE: all hardening flags + resp.set_cookie("auth_token", token, httponly=True, secure=True, samesite="Strict") + return resp \ No newline at end of file