diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 1b592e6..0166d67 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:** 59 -- **Expected detections:** 59 -- **`VULNERABLE:` markers:** 113 (individual lines a scanner should flag) -- **`SAFE:` markers:** 67 (lines a scanner must not flag — the false-positive control group) +- **Test cases:** 60 +- **Expected detections:** 60 +- **`VULNERABLE:` markers:** 114 (individual lines a scanner should flag) +- **`SAFE:` markers:** 68 (lines a scanner must not flag — the false-positive control group) - **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text -- **CWE categories:** 43 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-95, CWE-113, CWE-117, CWE-190, CWE-201, CWE-209, CWE-256, CWE-295, CWE-321, CWE-330, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-489, CWE-502, CWE-506, CWE-532, CWE-601, CWE-611, CWE-614, CWE-639, CWE-643, CWE-681, CWE-759, CWE-798, CWE-862, CWE-915, CWE-918, CWE-942, CWE-943, CWE-1236, CWE-1321, CWE-1333, CWE-1336, CWE-1357 +- **CWE categories:** 44 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-95, CWE-113, CWE-117, CWE-190, CWE-201, CWE-209, CWE-256, CWE-295, CWE-321, CWE-330, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-489, CWE-502, CWE-506, CWE-532, CWE-601, CWE-611, CWE-614, CWE-639, CWE-643, CWE-681, CWE-759, CWE-798, CWE-862, CWE-915, CWE-918, CWE-942, CWE-943, CWE-1236, CWE-1321, CWE-1333, CWE-1336, CWE-1357 ## How coverage is scored @@ -59,6 +59,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | 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 | | OS command injection via child_process.exec | [`rce-child-process.js`](../vulns/javascript/rce-child-process.js) | CWE-78 | critical | yes | 2 vuln / 1 safe | +| Session fixation when login reuses the anonymous session ID | [`session-fixation-login.js`](../vulns/javascript/session-fixation-login.js) | CWE-384 | high | yes | 1 vuln / 1 safe | | SQL injection via string concatenation | [`sqli-raw-concat.js`](../vulns/javascript/sqli-raw-concat.js) | CWE-89 | critical | yes | 2 vuln / 1 safe | | Server-side request forgery via user-supplied URL | [`ssrf-request-user-url.js`](../vulns/javascript/ssrf-request-user-url.js) | CWE-918 | high | yes | 2 vuln / 2 safe | | TLS certificate validation disabled on an HTTPS agent | [`tls-reject-unauthorized-false.js`](../vulns/javascript/tls-reject-unauthorized-false.js) | CWE-295 | high | yes | 1 vuln / 1 safe | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 1120d56..04ef479 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": 59, - "expected_detections": 59, - "vulnerable_markers": 113, - "safe_markers": 67, + "test_cases": 60, + "expected_detections": 60, + "vulnerable_markers": 114, + "safe_markers": 68, "languages": [ "dotenv", "go", @@ -47,6 +47,7 @@ "CWE-352", "CWE-362", "CWE-377", + "CWE-384", "CWE-489", "CWE-502", "CWE-506", @@ -621,6 +622,29 @@ 41 ] }, + { + "id": "js-session-fixation-login", + "file": "vulns/javascript/session-fixation-login.js", + "title": "Session fixation when login reuses the anonymous session ID", + "category": "javascript", + "language": "javascript", + "cwe": "CWE-384", + "cwes": [ + "CWE-384" + ], + "severity": "high", + "expected_detection": true, + "description": "An Express login handler attaches an authenticated user to the", + "detection_target": "Authentication state assigned to an existing session", + "safe_guard": "Wrapped in if (false) \u2014 authentication, session mutation, and", + "attribution": "line", + "vulnerable_lines": [ + 28 + ], + "safe_lines": [ + 43 + ] + }, { "id": "js-sqli-raw-concat", "file": "vulns/javascript/sqli-raw-concat.js", diff --git a/vulns/javascript/session-fixation-login.js b/vulns/javascript/session-fixation-login.js new file mode 100644 index 0000000..31cfdad --- /dev/null +++ b/vulns/javascript/session-fixation-login.js @@ -0,0 +1,58 @@ +/** + * @id js-session-fixation-login + * @test-case Session fixation when login reuses the anonymous session ID + * @cwe CWE-384 + * @severity high + * @language javascript + * @expected-detection true + * @description An Express login handler attaches an authenticated user to the + * existing anonymous session without regenerating its identifier. + * An attacker who supplied that identifier can reuse it after the + * victim signs in. + * @safe-guard Wrapped in if (false) — authentication, session mutation, and + * every response are unreachable dead code. Helpers are local and + * perform no I/O. + * @detection-target Authentication state assigned to an existing session + * without rotating the session identifier first. + */ + +'use strict'; + +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. +function loginVulnerable(req, res) { + if (false) { + const user = authenticate(req.body.username, req.body.password); + if (!user) { + return res.status(401).send('invalid credentials'); + } + req.session.userId = user.id; // VULNERABLE: CWE-384 — the anonymous session ID is reused after login + return res.json({ ok: true }); + } +} + +/** + * Safe counterpart — the scanner should NOT flag this. + * @expected-detection false + */ +function loginSafe(req, res) { + if (false) { + const user = authenticate(req.body.username, req.body.password); + if (!user) { + return res.status(401).send('invalid credentials'); + } + req.session.regenerate((error) => { // SAFE: rotate the identifier before attaching authenticated state + if (error) { + return res.status(500).send('login failed'); + } + req.session.userId = user.id; + return res.json({ ok: true }); + }); + } +} + +// Local inert placeholder so the file parses without an authentication service. +function authenticate(username, password) { + return username && password ? { id: 'fixture-user' } : null; +} + +module.exports = { loginVulnerable, loginSafe };