VNX-551: Incorrect Behavior Order: Authorization Before Parsing and Canonicalization
An access decision is taken on a request-supplied path or resource name that is only decoded, parsed or canonicalised afterwards, so the check judges a different string from the one the code uses.
| Field | Value |
|---|---|
| Rule ID | VNX-551 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Java, JavaScript / TypeScript, Python |
| CWE | CWE-551 |
| 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 | authz-before-canonicalization cwe-551 |
| Source | internal/sast/rules/vnx-551.rego |
Overview
VNX-551 maps to CWE-551: Incorrect Behavior Order: Authorization Before Parsing and Canonicalization. It reports an authorisation check on a value taken from the request when that same value is decoded, parsed or canonicalised in the lines after the check.
Why This Matters
A check on the raw text is not a check on what the code goes on to use. /%61dmin, /a/../admin and /admin;x fail a startsWith("/admin") test, then decode or normalise to /admin, and the protected resource is served. A file name that contains .. gets past a permission check written against the literal name, then normpath or getCanonicalPath turns it into a different file.
What Gets Flagged
The rule reports the check when:
- the value is assigned from the request (servlet
getRequestURI/getPathInfo/getParameter/getHeader, Django/Flaskrequest.path/args/GET, Expressreq.url/originalUrl/params/query) in the 20 lines above; - the check compares it with a
'/...'literal (startsWith,equals,includes,==,===) in a condition, or passes it to an authorisation call (authorize,checkPermission,hasPermission,canAccess,isAllowed…); - one of the next 15 lines decodes or canonicalises that same variable (
URLDecoder.decode,decodeURIComponent,unquote,normpath,realpath,getCanonicalPath,Paths.get(...).normalize(),path.normalize,urlparse,new URL).
String uri = request.getRequestURI();
if (uri.startsWith("/admin") && !isAdmin(request)) { // FLAGGED
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
String resource = URLDecoder.decode(uri, StandardCharsets.UTF_8);
name = request.args.get("name", "")
if not has_permission(current_user(), name): # FLAGGED
abort(403)
target = os.path.normpath(os.path.join(ROOT, name))
Not flagged: a value decoded or canonicalised before it is checked, a check on one variable followed by decoding another, values that do not come from the request, comments and string literals. Related rules: VNX-179 (checks for .. or < before decoding), VNX-289 (guards on the raw request path) and VNX-41 (directory-containment checks on non-canonical paths).
Remediation
- Decode, parse and canonicalise the value first:
URLDecoder.decodethenPaths.get(...).normalize(),unquotethenos.path.realpath,decodeURIComponentthenpath.posix.normalize. - Authorise the canonical form, and use exactly that form afterwards.
- Prefer framework path matching (
getServletPath(), Spring Security matchers) over hand-written prefix checks ongetRequestURI().
References
- CWE-551: Incorrect Behavior Order: Authorization Before Parsing and Canonicalization
- OWASP Path Traversal
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-551
// nosec vnx-551
Or record an ignore rule: vulnetix ignore add --rule vnx-551 --reason "...". See Suppressing findings.