VNX-1289: Improper Validation of Unsafe Equivalence in Input
Detects an uploaded file name checked against a denylist of dangerous extensions (php, exe, jsp, html, svg...) with a case-sensitive comparison, so shell.PHP passes. Python and JavaScript/TypeScript.
| Field | Value |
|---|---|
| Rule ID | VNX-1289 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Python, JavaScript / TypeScript |
| CWE | CWE-1289 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | input-validation-inconsistent cwe-1289 |
| Source | internal/sast/rules/vnx-1289.rego |
Overview
VNX-1289 maps to CWE-1289: Improper Validation of Unsafe Equivalence in Input. Input that is equivalent to a dangerous value, but not byte-identical to it, slips past a check written for the exact form. A file-upload denylist that compares the name case-sensitively is the most common case: the check rejects shell.php, while the web server still runs shell.PHP.
Reported: on an upload name (Flask/Werkzeug .filename, Django request.FILES[...].name (also f.name after f = request.FILES[...]), multer/formidable originalname/originalFilename, req.file(s) .name), directly or through a variable assigned from it in the 15 lines above (including os.path.splitext and path.extname results):
.endswith()/.endsWith()with a dangerous extension literal (php, phtml, phar, exe, bat, cmd, sh, jsp, asp/aspx, py, pl, cgi, js, html/htm/shtml/svg, dll, jar/war, ps1, vbs, hta, htaccess and similar), including a tuple split over lines;- membership in an inline or module-level list, tuple or set that holds such an extension (
in,.includes(),.has()); ==/===against one.
Not reported: a name lowered first (.lower(), .casefold(), .toLowerCase()), allowlists of harmless extensions, extension tests on names that are not uploads (such as os.walk results), comments and strings. Other unsafe equivalences (Unicode normalisation, trailing dots, alternate path separators) are not detected.
What Gets Flagged
f = request.files["file"]
if f.filename.endswith(".php"): # FLAGGED: "shell.PHP" passes
abort(400)
const ext = path.extname(req.file.originalname);
if (['.php', '.jsp', '.html'].includes(ext)) { // FLAGGED
return res.status(400).end();
}
What Is Not Flagged
ext = os.path.splitext(f.filename)[1].lower()
if ext in [".exe", ".php"]:
abort(400)
Remediation
- Normalise (lower-case) the name before comparing it.
- Prefer an allowlist of permitted extensions and content types over a denylist.
- Store uploads outside the web root under generated names.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-1289
// nosec vnx-1289
Or record an ignore rule: vulnetix ignore add --rule vnx-1289 --reason "...". See Suppressing findings.