CWE-1289 is the weakness behind a huge number of validation bypasses, and it is easy to miss because the validation code usually looks fine. The product receives a value that is used as a resource identifier or other kind of reference, and it either does not validate that the value is equivalent to something unsafe, or does so incorrectly.
The word to focus on is equivalent. Your check compares the input to a bad value. A downstream component treats a different string as the same bad value. The check passes, the downstream component acts, and the attacker wins.
It affects any language and any platform. If you accept hostnames, file names, extensions, URLs, redirect targets, or process names, you have a surface for it.
How it happens
MITRE gives the classic illustration. A simple cross-site scripting filter validates that an input contains no script tags, but uses case-sensitive matching. HTML is case-insensitive when browsers process it, so an attacker submits the same tag with mixed case. The filter sees a harmless string. The browser sees a script tag.
The general pattern has three parts:
- A check runs at one layer, and it compares against a fixed representation of the unsafe value.
- The value then moves to a lower layer or another component, which normalizes it differently. Case folding, whitespace handling, character decoding and path resolution are common places.
- The two layers disagree about what counts as the same value, and the disagreement is the vulnerability.
Here is the shape in code. This is a deliberately vulnerable pattern, shown for learning.
# VULNERABLE: case-sensitive deny check on a file extension
BLOCKED = {".php", ".exe"}
def allowed_upload(filename: str) -> bool:
ext = filename[filename.rfind("."):]
return ext not in BLOCKED
allowed_upload("shell.PHP") # True, but the server may treat it as PHP
The observed examples in the MITRE record show real products making these mistakes:
- CVE-2021-39155: a microservice integration and management platform compares the hostname in the HTTP Host header in a case-sensitive way, which allows bypass of the authorization policy using a hostname with mixed case or other variations.
- CVE-2020-11053: a Go-based OAuth2 reverse proxy can send the authenticated user to another site at the end of the authentication flow. A redirect URL with HTML-encoded whitespace characters bypasses the validation and leads to a redirect to a malicious site.
- CVE-2005-0269: forum software only verifies file extensions made of all lowercase letters, so uploads with uppercase letters in the extension get through.
- CVE-2001-1238: Task Manager does not allow local users to end certain processes, but the protection was bypassed for process names written with uppercase letters.
The oldest example is from 2001 and the newest is a modern OAuth2 proxy. The mistake does not age.
What an attacker gains
MITRE lists the consequence as other, varying by context. That is honest, since the weakness is a way in rather than a single outcome. What the attacker gets depends on what the bypassed check was protecting.
From the observed examples you can see a spread: bypassing an authorization policy, redirecting a signed-in user to a malicious site, uploading arbitrary files, and ending protected processes. In each case the validation was the only barrier, and it did not hold.
How to find it
The record lists no detection methods, so use these practical checks.
- List every place where input is compared to a fixed string or pattern as a security decision: allowlists, denylists, host checks, extension checks, redirect checks.
- For each, ask what the next component does with the value. Does it fold case, decode entities, trim whitespace, normalize paths, or resolve Unicode forms?
- Test variants. Mixed case, percent-encoding, HTML entities, extra whitespace, trailing dots, and alternate Unicode forms will find most of these gaps quickly.
- Run static analysis over the code that handles identifiers, since comparisons against string literals are easy to flag.
For the dependency side, a library that validates input for you can carry the same flaw. The free in-browser scan at gold.safeguard.sh/scan checks a manifest on your machine, with no login and up to 100 dependencies, and reachability analysis helps you see whether your code actually calls the affected function.
How to fix it
MITRE's mitigation is an input validation strategy for the implementation phase, rated high effectiveness. Its core points, in plain terms:
- Assume all input is malicious.
- Use an accept known good strategy. Keep a list of acceptable inputs that strictly conform to the specification, and reject anything else or transform it into something that does.
- When validating, consider every relevant property: length, type, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and business rules.
- Do not rely only on looking for malicious inputs. This will likely miss at least one bad input, especially if the environment changes. Denylists can still help detect attacks or reject inputs so malformed that they should be dropped outright.
MITRE adds a business rule example: boat may be syntactically valid because it is alphanumeric, but it is not valid if the field should only ever contain colors such as red or blue.
Applied to the upload example, normalize first and allow only what you expect:
# FIX: normalize, then accept known good
ALLOWED = {".png", ".jpg", ".pdf"}
def allowed_upload(filename: str) -> bool:
ext = os.path.splitext(filename)[1].lower()
return ext in ALLOWED
And for a redirect target in JavaScript, parse it and compare the parsed host to an exact allowlist, instead of searching the raw string:
// FIX: compare the parsed host, not the raw string
const ALLOWED_HOSTS = new Set(["app.example.com"]);
function safeRedirect(target) {
try {
const url = new URL(target, "https://app.example.com");
return ALLOWED_HOSTS.has(url.hostname) ? url.href : "/";
} catch {
return "/";
}
}
The principle is to compare in the same form the downstream component will use. Decide on one canonical form, convert to it once, and check after conversion.
Try it yourself
A static code scan is a good way to see how comparisons against literal strings get flagged. Step through the demo below.
Click through it at your own pace, or open it full screen.
Quick check
Why does a case-sensitive check fail against a browser?
HTML is case-insensitive when processed by browsers, so a differently cased tag still runs even though a case-sensitive filter did not match it.
Is a denylist ever useful here?
Only as a supplement. MITRE says to use accept known good as the base, and denylists can help detect attacks or reject inputs that are so malformed they should be dropped.
Where should normalization happen?
Before the security check, converting to the same canonical form the downstream component will use.