CWE-173 is the failure to handle input that uses an alternate encoding which is valid for the component receiving it. The product expects one spelling of a value and is given a different spelling that means the same thing downstream.
Anything that makes decisions based on names, such as file names, URL paths, host names and user names, is at risk. MITRE lists access control bypass and protection mechanism bypass as the impact.
How it happens
A character or name can often be written more than one way. A slash can be a plain slash or a percent encoded one. A letter can be written directly or as an escape. Components along a request path may each accept some of these forms.
A security check that compares input to a forbidden value works only if the input is in the same form as the forbidden value. Change the form and the comparison fails, but the downstream component still resolves it to the same thing.
# Vulnerable: compares only the plain form
BLOCKED = "/admin"
def allowed(path: str) -> bool:
return not path.startswith(BLOCKED)
A request for /%61dmin is the same as /admin after a percent decode, but it does not start with the blocked text.
The record lists no observed example CVEs for this entry. Related cases for decoding order are described under CWE-172 and CWE-174.
What an attacker gains
The recorded consequence is a bypass of protection mechanisms in the access control area. An attacker reaches resources or functions that a rule meant to deny.
How to find it
MITRE lists no detection methods. Test it.
- Send protected names in alternate forms: percent encoded, mixed case where the platform folds case, and other encodings your stack accepts.
- Find every comparison against a fixed string in an access decision.
- Check whether the component that checks is the same as the component that uses the value.
How to fix it
MITRE gives two moves.
- At design time, avoid making decisions based on the names of resources, such as files, if those resources can have alternate names. Decide on identity, not on spelling. Use identifiers or permissions attached to the resource itself.
- At implementation time, assume all input is malicious and use an accept known good strategy. Reject input that does not strictly conform, or transform it into a conforming form. Check length, type, range, syntax and consistency.
In code, convert to one canonical form first and compare after.
from urllib.parse import unquote
from posixpath import normpath
def canonical(path: str) -> str:
return normpath(unquote(path))
def allowed(path: str) -> bool:
return not canonical(path).startswith("/admin")
Decode once. Decoding repeatedly causes the double decoding problem.
Quick check
Why is comparing to a blocked name weak?
Because the input may use another valid spelling of the same name. Canonicalize first, or better, enforce access on the resource itself.