VNX-178: Improper Handling of Case Sensitivity
Reports an uploaded file's name or extension compared case-sensitively against a dangerous extension (php, jsp, aspx, exe, sh, html, svg, htaccess...), a deny check that "shell.PHP" bypasses.
| Field | Value |
|---|---|
| Rule ID | VNX-178 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Go, Java, JavaScript / TypeScript, Python, PHP |
| CWE | CWE-178 |
| 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 | case-sensitivity validation file-upload cwe-178 |
| Source | internal/sast/rules/vnx-178.rego |
Overview
VNX-178 maps to CWE-178: Improper Handling of Case Sensitivity. It reports a case-sensitive comparison of a file name or extension against a dangerous extension (php/phtml/phar, jsp/jspx, asp/aspx, exe/dll, sh/bat/cmd/ps1, cgi/pl/py/rb, js, html/svg, htaccess, war/jar) in a Python, JavaScript/TypeScript, Java, Go or PHP file that handles uploads (request.files, multer originalname, MultipartFile, FormFile, $_FILES). The comparison forms are endswith/endsWith/HasSuffix/str_ends_with, ==/===/!=, equals, membership (in [...], in_array, includes, contains), membership in a deny-list collection of those extensions defined in the same file (ext in BLOCKED_EXTENSIONS, in_array($ext, $blocked)), and a case-sensitive regular expression (/\.(php|jsp)$/.test(name) without the i flag, re.search(r"\.(php|exe)$", name) without re.I, Java matches without (?i)). The line must name the value as a file name, extension, suffix or path.
Not reported: a format, language or output parameter compared with "html" or "py"; a value lower- or upper-cased on the same line, or assigned from lower()/toLowerCase()/strings.ToLower/strtolower/casefold in the ten lines above; equalsIgnoreCase/EqualFold; allow-lists of harmless extensions; files that do not handle uploads; the comparison inside a string literal; comments. A case-sensitive comparison against a role name such as "admin" fails closed and is not reported.
Why This Matters
Web servers and interpreters usually match extensions without regard to case, or run on case-insensitive file systems. A filter that rejects .php but accepts .PHP or .pHp lets an attacker upload a script the server will still execute.
What Gets Flagged
# FLAGGED
if filename.endswith((".php", ".exe", ".sh")):
abort(400)
// FLAGGED
const ext = path.extname(file.originalname);
if (ext === '.php' || ext === ".jsp") { ... }
// FLAGGED
if strings.HasSuffix(header.Filename, ".php") { ... }
// FLAGGED: no i flag
if (/\.(php|jsp|exe|sh)$/.test(file.originalname)) { ... }
# NOT FLAGGED: normalised first
ext = os.path.splitext(f.filename)[1].lower()
if ext in [".php", ".phtml"]:
abort(400)
Remediation
- Lower-case the file name or extension before any comparison.
- Prefer an allow-list of the extensions you accept to a deny-list of the ones you fear.
- Store uploads outside the web root under a generated name, so the uploaded name never decides how the file is served.
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-178
// nosec vnx-178
Or record an ignore rule: vulnetix ignore add --rule vnx-178 --reason "...". See Suppressing findings.