VNX-626: Request input with an appended file extension used as a file path (null byte truncation)
Reports PHP and Java file paths built from request input with a fixed extension appended, where a NUL byte can truncate the path and drop the suffix.
| Field | Value |
|---|---|
| Rule ID | VNX-626 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | PHP, Java |
| CWE | CWE-626 |
| CAPEC | CAPEC-97 |
| ATT&CK | T1557 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | null-byte path-traversal cwe-626 |
| Source | internal/sast/rules/vnx-626.rego |
Overview
In languages that interface with C-based libraries (PHP, early Python versions), a null byte (%00 or \0) in a string terminates it at the C level while the higher-level language sees the full string. Attackers append %00 to filenames to strip validation suffixes: ../../../../etc/passwd%00.jpg passes an .jpg extension check but opens /etc/passwd.
Why This Matters
PHP versions before 5.3.4 were notoriously vulnerable: include($_GET['page'] . '.php') could be bypassed with page=../../../../etc/passwd%00 to read arbitrary files. Even modern applications using C-extension file paths may be vulnerable if null bytes are not stripped from user input.
What Gets Flagged
<?php
// FLAGGED: User input used directly in include without null byte sanitization
include($_GET['page'] . '.php');
// FLAGGED: the suffix is the only type restriction
$doc = $_GET['doc'];
echo file_get_contents("docs/{$doc}.html");
// FLAGGED
File f = new File(reportDir, request.getParameter("name") + ".txt");
Not flagged: input passed through basename(), an int cast, str_replace(chr(0), ...) or an allow-list check (in_array, preg_match, String.matches) before use; paths with no appended extension (path traversal and file inclusion rules such as VNX-98 cover those); constants; comments and string literals. Python, Ruby, Go and Node.js reject NUL bytes in paths and are not covered.
Remediation
Strip null bytes from user input and validate after sanitization:
<?php
// SAFE: Strip null bytes and validate extension
$page = str_replace(chr(0), '', $_GET['page']);
if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $page)) {
die('Invalid page name');
}
include($BASE_DIR . $page . '.php');
# SAFE: Strip null bytes, use basename, validate
path = request.GET.get('file', '')
path = path.replace('\x00', '')
path = os.path.basename(path) # strip directory traversal
full_path = os.path.realpath(os.path.join(base_dir, path))
if not full_path.startswith(os.path.realpath(base_dir)):
return HttpResponseForbidden()
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-626
// nosec vnx-626
Or record an ignore rule: vulnetix ignore add --rule vnx-626 --reason "...". See Suppressing findings.