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
11 changes: 6 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:** 122
- **Expected detections:** 122
- **`VULNERABLE:` markers:** 227 (individual lines a scanner should flag)
- **`SAFE:` markers:** 138 (lines a scanner must not flag — the false-positive control group)
- **Test cases:** 124
- **Expected detections:** 124
- **`VULNERABLE:` markers:** 231 (individual lines a scanner should flag)
- **`SAFE:` markers:** 141 (lines a scanner must not flag — the false-positive control group)
- **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text
- **CWE categories:** 86 — 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-311, 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-522, 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-732, 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
- **CWE categories:** 87 — 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-311, 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-409, 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-522, 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-732, 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

## How coverage is scored

Expand Down Expand Up @@ -152,6 +152,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`.
| XPath injection via string interpolation | [`xpath-injection.py`](../vulns/python/xpath-injection.py) | CWE-643 | high | yes | 1 vuln / 1 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 |
| XML external entity expansion in an insecure parser | [`xxe-lxml-resolve.py`](../vulns/python/xxe-lxml-resolve.py) | CWE-611 | high | yes | 2 vuln / 1 safe |
| Path traversal through zip archive entry names (Zip Slip) | [`zip-slip-traversal.py`](../vulns/python/zip-slip-traversal.py) | CWE-409 | high | yes | 2 vuln / 1 safe |

## Ruby

Expand Down
33 changes: 29 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": 122,
"expected_detections": 122,
"vulnerable_markers": 227,
"safe_markers": 138,
"test_cases": 124,
"expected_detections": 124,
"vulnerable_markers": 231,
"safe_markers": 141,
"languages": [
"dotenv",
"go",
Expand Down Expand Up @@ -65,6 +65,7 @@
"CWE-377",
"CWE-384",
"CWE-400",
"CWE-409",
"CWE-434",
"CWE-441",
"CWE-460",
Expand Down Expand Up @@ -2772,6 +2773,30 @@
42
]
},
{
"id": "py-zip-slip-traversal",
"file": "vulns/python/zip-slip-traversal.py",
"title": "Path traversal through zip archive entry names (Zip Slip)",
"category": "python",
"language": "python",
"cwe": "CWE-409",
"cwes": [
"CWE-409"
],
"severity": "high",
"expected_detection": true,
"description": "A zip archive from an untrusted source is extracted without",
"detection_target": "zipfile.ZipFile.extractall / extract / namelist-derived",
"safe_guard": "All extraction wrapped in `if False:` (unreachable dead code).",
"attribution": "line",
"vulnerable_lines": [
32,
40
],
"safe_lines": [
52
]
},
{
"id": "rb-cwe480-credential-comparison",
"file": "vulns/ruby/cwe-480-ruby.rb",
Expand Down
60 changes: 60 additions & 0 deletions vulns/python/zip-slip-traversal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
@id py-zip-slip-traversal
@test-case Path traversal through zip archive entry names (Zip Slip)
@cwe CWE-409
@severity high
@language python
@expected-detection true
@description A zip archive from an untrusted source is extracted without
checking that each member name stays inside the destination
directory. An entry named `../../etc/cron.d/backdoor` escapes the
target dir, letting the attacker write arbitrary files on the
host. The safe counterpart validates every member against the
resolved destination root. Detection target is an extract call
where entry names are used for write paths with no containment
check.
@safe-guard All extraction wrapped in `if False:` (unreachable dead code).
No archive is read from disk; the ZipFile is built from a
synthetic bytes literal.
@detection-target zipfile.ZipFile.extractall / extract / namelist-derived
writes where ZipInfo.filename is not checked for
../ or absolute-path traversal before being resolved.

NEVER RUN IN PRODUCTION - intentional test case for scanner validation.
"""

import zipfile
import io


def extract_zip_slip(zip_bytes: bytes, dest: str) -> None:
if False:
# VULNERABLE: CWE-409 - entry names trusted, no containment check
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
zf.extractall(dest)
return None


def extract_zip_slip_single(zip_bytes: bytes, dest: str, member: str) -> None:
if False:
# VULNERABLE: CWE-409 - single member, still no traversal check
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
zf.extract(member, dest)
return None


def extract_zip_safe(zip_bytes: bytes, dest: str) -> None:
"""Safe counterpart - the scanner should NOT flag this.

@expected-detection false
"""
if False:
# SAFE: validate each member stays within the destination root
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
for info in zf.infolist():
import os
target = os.path.realpath(os.path.join(dest, info.filename))
if not target.startswith(os.path.realpath(dest) + os.sep):
raise ValueError("member escapes destination: " + info.filename)
zf.extractall(dest)
return None
Loading