Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions docs/VULNERABILITY_CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:** 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:** 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:** 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

Expand All @@ -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 |
Expand All @@ -56,10 +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 |

Expand Down
159 changes: 155 additions & 4 deletions vulns/VULNERABILITY_CATALOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": 38,
"expected_detections": 38,
"vulnerable_markers": 92,
"safe_markers": 46,
"languages": [
"dotenv",
"go",
Expand All @@ -32,17 +32,21 @@
"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"
]
Expand Down Expand Up @@ -222,6 +226,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",
Expand Down Expand Up @@ -416,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",
Expand Down Expand Up @@ -491,6 +569,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",
Expand All @@ -516,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",
Expand Down
54 changes: 54 additions & 0 deletions vulns/java/xxe-document-builder.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
76 changes: 76 additions & 0 deletions vulns/javascript/csrf-missing-token.js
Original file line number Diff line number Diff line change
@@ -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,
};
Loading
Loading