Percent Encoding and Canonicalization for PenTest+, CySA+, Security+
Percent Encoding and Canonicalization
· @Ken
Title: Percent Encoding and Canonicalization: Why Decode Order Decides Whether a Filter Works
SEO Title: Percent Encoding and Canonicalization for PenTest+, CySA+, Security+
Meta Description: How percent encoding hides attacks, why double encoding defeats filters, and the canonicalization rule that fixes it. For PenTest+, CySA+, and Security+.
What Percent Encoding Is
A URL can only safely carry a limited set of characters. Some are reserved because they have structural meaning — / separates path segments, ? starts the query string, & separates parameters, # marks a fragment. Others are unsafe because they break parsing or do not survive transmission, like spaces and control characters.
Percent encoding, also called URL encoding, solves this by replacing a character with a % followed by its two-digit hexadecimal byte value. A space becomes %20. A forward slash becomes %2F. An ampersand becomes %26. Unreserved characters — letters, digits, -, ., _, ~ — never need encoding.
This is ordinary, legitimate web plumbing. When you search for "network plus" and the URL shows q=network%20plus, that is percent encoding doing its job: carrying a space through a field where a literal space would break the parse.
The security relevance comes from a single property. Percent encoding changes how a string looks without changing what it means, and different components in a request path decode at different times.
How Attackers Use It
A security filter looking for a dangerous string can only block what it recognizes. Percent encoding changes the string without changing the outcome, so the filter sees something harmless and the application receives the attack.
Directory traversal. A filter blocking ../ never sees %2e%2e%2f. Same bytes after decoding, different text on the way in.
Injection payloads. A single quote becomes %27, a semicolon %3B, a space %20. ' OR 1=1-- arrives as %27%20OR%201%3D1--, which matches no SQL-injection signature written against the literal form.
Cross-site scripting. <script> becomes %3Cscript%3E, stepping past a filter watching for angle brackets.
Double encoding. The most important variant. Encode the % itself — % is %25, so %2e becomes %252e. A device that decodes once sees %2e, which is not . and passes. The application then decodes again, and the final value is .. Anywhere a request is decoded more times downstream than upstream, this works.
Mixed and non-standard forms. Partial encoding (..%2f), case variation (%2F versus %2f), UTF-8 overlong sequences (%c0%af for /), and on Windows backslash equivalents. Each is a different way of writing the same character, and each defeats a filter matching one exact spelling.
The theme throughout: blocklists match text, and text has unlimited spellings.
The Rule That Explains All of It: Canonicalization Order
Canonicalization is the process of reducing input to one standard, fully decoded form before doing anything with it. Nearly every evasion above exists because a system validated input before canonicalizing it.
Compare the two orders.
Validate, then decode — broken:
- Input arrives as
%252e%252e%252f - The filter checks it against a blocklist. No match; it looks like ordinary text.
- The framework decodes it once →
%2e%2e%2f - The file API decodes it again →
../ - Traversal succeeds.
Decode fully, then validate — correct:
- Input arrives as
%252e%252e%252f - Decode repeatedly until the value stops changing →
../ - Normalize the path — resolve
.and.., unify separators, apply Unicode normalization. - Now validate. The traversal is plainly visible and rejected.
Validate the canonical form, never the wire form.
Two cautions that follow from this. Decoding must be repeated until the output stabilizes, because a single pass is exactly what double encoding is built to defeat. And every component in the chain — load balancer, WAF, web server, application framework, file system API — should agree on how many times the value has been decoded. Disagreement between any two of them is the vulnerability.
Detecting and Defending Against It
In logs, encoded attacks have a look of their own. Normal browser traffic contains a little percent encoding — a %20 here, a %40 for an at-sign. Attack traffic contains a lot. Watch for:
- A high density of
%characters in a single request, especially in a path. %25anywhere — a literal percent sign is rare in legitimate input and is the signature of double encoding.- Encoded sequences that decode to shell metacharacters, path separators, quotes, or angle brackets.
- The same endpoint hit repeatedly with different encodings of the same value, suggesting someone is working through a bypass list.
Always decode before you analyze. A grep for ../ across web logs will miss every encoded attempt. Decode the field first, repeatedly, then search. This is the most common analyst mistake with this topic.
On defense:
- Canonicalize, then validate — the rule from the previous section.
- Allowlist what is permitted rather than blocklisting what is forbidden. An allowlist is immune to novel spellings.
- Use safe APIs. Parameterized queries, framework path-resolution functions, and contextual output encoding remove the class rather than filtering it.
- Make the chain agree. Ensure the WAF, web server, and application decode consistently, and reject requests containing
%25where it has no business appearing. - Treat the WAF as a delay, not a fix. Normalization rules help, but bypasses are found continuously.
PenTest+ (PT0-003) Exam Tips
The heaviest coverage — here, encoding is an active technique.
- Know the common values on sight:
%20space,%2F/,%5C\,%2E.,%27',%22",%3C<,%3E>,%3B;,%26&,%25%. - Double encoding is the one they test. Be able to decode
%252e%252e%252fin two passes and explain why a single-decode filter lets it through. - Encoding is an evasion technique, listed alongside obfuscation, case variation, and null bytes. Expect a question giving you a blocked payload and asking what to try next.
- Recognize encoded payloads in a question stem and decode them to identify the underlying attack — traversal, SQLi, XSS, command injection.
- Write the finding correctly. The root cause is validating before canonicalizing, and the remediation is canonicalize-then-allowlist plus safe APIs — not "add the encoded string to the WAF."
CySA+ (CS0-004) Exam Tips
The defensive mirror — you are reading the encoded string, not writing it.
- Decode log entries as a routine step. Given a raw web log line, translate the encoded portion and state what was attempted.
- Heavy encoding is itself an indicator. Normal traffic is lightly encoded; a request full of
%sequences is anomalous regardless of what it decodes to. %25means double encoding, and double encoding means deliberate filter evasion. That distinction separates a scanner from a targeted attempt.- Know why a signature missed it. A common scenario: the IDS logged nothing, but the application was compromised. The answer is usually that the signature matched literal text and the payload was encoded.
- Recommend the right control. Canonicalization before validation, input allowlisting, and WAF normalization rules — in that order of durability.
Security+ (SY0-701) Exam Tips
Lighter coverage, focused on the concept rather than the hex table.
- Input validation and why it fails. Validation applied to uncanonicalized input is the textbook example of a control that exists but does not work.
- Directory traversal and that encoding is how it slips past naive filters.
- Allowlisting beats blocklisting. This principle is tested repeatedly, and encoding is the clearest illustration of why.
- Obfuscation is an evasion category, alongside encoding, encryption, and packing.
Outside CompTIA, CEH covers percent encoding explicitly as an IDS and WAF evasion technique, with more depth on alternative encodings. Network+ touches URL structure only as addressing, with no security angle.
Key Takeaways
- Percent encoding replaces a character with
%plus its hex byte value — legitimate web plumbing, not an attack by itself. - It changes how a string looks without changing what it means, which is exactly what defeats a text-matching filter.
- Double encoding (
%252e) is the variant to know. One decode pass leaves%2e; a second yields.. - The root cause is always order: validating before canonicalizing. Decode until the value stops changing, normalize, then validate.
- Allowlist rather than blocklist. There are unlimited spellings of a forbidden string and a finite set of permitted ones.
- Decode before you analyze logs, or you will search for attacks in a form they never appear in.
- PenTest+ tests using it, CySA+ tests reading it, Security+ tests the principle behind it.
Comments
Post a Comment