How an Encoding Mismatch Between Two Parsers Defeats an Access Rule
When the layer that checks a path and the layer that routes it decode a URL differently, one encoded character slips past authorization. The mechanism behind CVE-2026-76504 and how to close it.
Two parsers, one URL, two answers
Most web systems look at a request path more than once. One layer decides whether a rule applies: is this path public, does it need authentication, should a filter block it. A later layer actually dispatches the request to code that does the work. For an access rule to be sound, both layers have to agree on what the path is. When they disagree, an attacker can craft a URL that the rule-checking layer reads as harmless and the dispatching layer reads as the thing you wanted to protect.
That disagreement is exactly what drove the Cisco Catalyst SD-WAN Manager bypass tracked as CVE-2026-76504, where a single percent-encoded character let an unauthenticated request reach an admin-only endpoint. Cisco classified it as CWE-177, improper handling of URL encoding, and CISA labelled it a "hex encodingHex Encoding🛡️Representing a character in a URL or protocol as a percent sign followed by its two-digit hexadecimal byte value, such as %6a for the letter j. Improper or inconsistent handling of hex encoding (CWE-177) lets an attacker write a protected path in a form that an access rule fails to recognize but a downstream handler still decodes and serves." flaw. The product is specific; the class of bug is everywhere. This article explains the mechanism so you can recognize it in your own stack.
What encoding actually is
A URL can only safely carry a limited set of characters, so the rest are written as a percent sign followed by two hex digits. The letter `j` is `%6a`. A space is `%20`. A forward slash is `%2f`. Decoding a URL means walking the string and replacing each of these escapes with the byte it represents. The crucial point is that `%6a` and `j` are supposed to mean the same thing: `/%6a_security_check` and `/j_security_check` are the same resource once decoded.
The trouble starts when one component decodes before it compares and another compares before it decodes, or when the two components decode a different number of times. Suppose an access rule says "require authentication for any path equal to `/j_security_check`." If that rule runs its comparison against the raw, still-encoded string `/%6a_security_check`, the strings do not match, so the rule concludes the path is not the protected one and lets the request through unauthenticated. The dispatcher downstream then decodes the escape, sees `/j_security_check`, and happily serves the protected handler. One request, two readings, and the gate never closed.
Why this is not the same as a WAF signature bypass
It is easy to lump this together with evading a web application firewallFirewall🌐Security system that monitors and controls network traffic based on predetermined rules. by encoding an attack payload, which we covered in Why URL Encoding Lets Attackers Slip Past String-Matching WAF Rules. The shapes rhyme but the targets differ. A WAF bypass hides malicious content, such as a SQL fragment or a traversal sequence, from a pattern matcher that is scanning for bad strings. The encoding-mismatch bug here is not about hiding a payload at all. The request is ordinary; what is manipulated is the application's own routing-versus-authorization decision about a perfectly normal path. The defense that stops a WAF evasion, better signatures, does nothing here, because there is no malicious signature to catch. The fix has to live in how the application canonicalizes paths.
It is also distinct from simply forgetting to protect an endpoint, the failure described in Why a Missing Auth Check on One API Endpoint Bypasses the Whole Login Page. In that case the check never existed. Here the check exists and is correct in intent; it is defeated because it operates on a different representation of the path than the code it is guarding.
Canonicalize once, then decide
The durable fix for this entire class is canonicalizationCanonicalization🛡️Reducing an input such as a URL path to a single normal form, by decoding escapes, collapsing duplicate separators and resolving relative segments, before any security decision is made about it. Canonicalizing once and then matching prevents the parser disagreements that produce encoding-based authorization bypasses.: reduce every incoming path to one normal form before anything makes a security decision about it. Decode all percent escapes, collapse duplicate slashes, resolve any `.` and `..` segments, normalize case where the underlying system is case-insensitive, and only then run both the authorization check and the routing against that single canonical string. If the rule and the dispatcher share the same normalized value, there is no gap to wedge an encoded character into.
Three practical rules follow from that. First, never compare a security-relevant path against a raw, undecoded request string; the comparison must happen after normalization. Second, decode exactly once, and reject input that still contains percent escapes after a single decode pass, because a second round of decoding is almost always an attack and almost never legitimate. Third, prefer a positive model: match the request to a known route first, then look up the policy attached to that route, so authorization keys off the resolved handler rather than off a string you hope matches.
How to spot it in your environment
You can find candidates for this bug by looking for places where two different pieces of software touch the same path. A reverse proxyReverse Proxy🛡️A server that sits in front of one or more backend services, terminating client connections and forwarding requests to the backend. It is the standard place to add authentication, TLS and access control to a service that lacks its own, without modifying the application. that applies access rules in front of an application server is the classic setup, and the Catalyst SD-WAN Manager case fits the pattern, with a service proxy logging the requests that reach the back end. Any time an authorization decision is made by one layer and enforced by routing in another, ask whether they decode identically.
When you review logs after a disclosure like this, the fingerprint is a request whose path contains a percent escape in a spot where no legitimate client would encode it, landing on a sensitive endpoint. Cisco's guidance pointed defenders at exactly that: encoded variants of the login path reaching admin functions from unexpected addresses. The same question drives a broader exercise, working out which of your management interfaces an outsider can even reach, which we take up in How to Find Which of Your Admin Consoles Are Reachable From the Internet. And when the vendor offers no mitigating setting, as Cisco did not here, the response options narrow fast, a situation we cover in Why 'No Workaround Available' Should Change How You Triage a Patch.