Skip to content

Commit e6c6013

Browse files
test: rebase PR #192 onto master; catalog regenerated (#192)
1 parent 327224c commit e6c6013

3 files changed

Lines changed: 82 additions & 0 deletions

File tree

docs/VULNERABILITY_CATALOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`.
123123
| Login brute force enabled by absent rate limiting | [`missing-login-rate-limit.py`](../vulns/python/missing-login-rate-limit.py) | CWE-307 | high | yes | 3 vuln / 1 safe |
124124
| 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 |
125125
| Open redirect via unvalidated next parameter | [`open-redirect.py`](../vulns/python/open-redirect.py) | CWE-601 | medium | yes | 2 vuln / 1 safe |
126+
| CORS origin validation bypass (reflected origin without allow-list) | [`origin-validation-bypass.py`](../vulns/python/origin-validation-bypass.py) | CWE-346 | medium | yes | 2 vuln / 1 safe |
126127
| 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 |
127128
| 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 |
128129
| Permissive cross-origin policy with credentialed requests | [`permissive-cors-credentials.py`](../vulns/python/permissive-cors-credentials.py) | CWE-942 | medium | yes | 2 vuln / 1 safe |

vulns/VULNERABILITY_CATALOG.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,6 +2084,30 @@
20842084
51
20852085
]
20862086
},
2087+
{
2088+
"id": "py-origin-validation-bypass",
2089+
"file": "vulns/python/origin-validation-bypass.py",
2090+
"title": "CORS origin validation bypass (reflected origin without allow-list)",
2091+
"category": "python",
2092+
"language": "python",
2093+
"cwe": "CWE-346",
2094+
"cwes": [
2095+
"CWE-346"
2096+
],
2097+
"severity": "medium",
2098+
"expected_detection": true,
2099+
"description": "The server echoes the request Origin header into",
2100+
"detection_target": "Flask / Starlette / FastAPI / Django response where",
2101+
"safe_guard": "All response construction wrapped in `if False:` (unreachable",
2102+
"attribution": "line",
2103+
"vulnerable_lines": [
2104+
30,
2105+
39
2106+
],
2107+
"safe_lines": [
2108+
51
2109+
]
2110+
},
20872111
{
20882112
"id": "py-path-traversal-open",
20892113
"file": "vulns/python/path-traversal-open.py",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
@id py-origin-validation-bypass
3+
@test-case CORS origin validation bypass (reflected origin without allow-list)
4+
@cwe CWE-346
5+
@severity medium
6+
@language python
7+
@expected-detection true
8+
@description The server echoes the request Origin header into
9+
Access-Control-Allow-Origin without verifying it against an
10+
allow-list. Any site can make credentialed requests and read the
11+
response. The safe counterpart checks the origin against a static
12+
allow-list before reflecting. Detection target is a response
13+
header write where ACAO = request Origin (or "*") and
14+
Access-Control-Allow-Credentials = true, with no allow-list check.
15+
@safe-guard All response construction wrapped in `if False:` (unreachable
16+
dead code). No HTTP response is sent.
17+
@detection-target Flask / Starlette / FastAPI / Django response where
18+
headers["Access-Control-Allow-Origin"] = request.headers.get("Origin")
19+
(or "*") AND headers["Access-Control-Allow-Credentials"] = "true"
20+
with no prior allow-list membership test.
21+
22+
NEVER RUN IN PRODUCTION - intentional test case for scanner validation.
23+
"""
24+
25+
from flask import Flask, request, make_response
26+
27+
28+
def cors_reflect_origin(resp):
29+
if False:
30+
# VULNERABLE: CWE-346 - reflects Origin without validation
31+
origin = request.headers.get("Origin", "")
32+
resp.headers["Access-Control-Allow-Origin"] = origin
33+
resp.headers["Access-Control-Allow-Credentials"] = "true"
34+
return resp
35+
36+
37+
def cors_wildcard_credentials(resp):
38+
if False:
39+
# VULNERABLE: CWE-346 - wildcard with credentials
40+
resp.headers["Access-Control-Allow-Origin"] = "*"
41+
resp.headers["Access-Control-Allow-Credentials"] = "true"
42+
return resp
43+
44+
45+
def cors_allowlist(resp):
46+
"""Safe counterpart - the scanner should NOT flag this.
47+
48+
@expected-detection false
49+
"""
50+
if False:
51+
# SAFE: validate against allow-list
52+
allowlist = {"https://app.internal.invalid", "https://admin.internal.invalid"}
53+
origin = request.headers.get("Origin", "")
54+
if origin in allowlist:
55+
resp.headers["Access-Control-Allow-Origin"] = origin
56+
resp.headers["Access-Control-Allow-Credentials"] = "true"
57+
return resp

0 commit comments

Comments
 (0)