XSS Filter Bypass — Encoding and Obfuscation
Web Application Firewalls (WAFs) and server-side input filters are frequently deployed as the first line of defence against Cross-Site Scripting (XSS). While they raise the cost of a successful attack, they are not a complete solution. A determined attacker who understands how browsers parse HTML and how filter engines operate can routinely circumvent denylist-based controls. This lesson examines the most reliable bypass families and explains the underlying parser behaviour that makes each one work.
Why Filters Fail
Most XSS filters operate as string-matching denylists. They scan inbound input for patterns such as <script, onerror=, or javascript: and block or sanitise the request when a match is found. The fundamental weakness is that HTML parsers and JavaScript engines accept a far richer set of syntactically equivalent inputs than any denylist can enumerate. Attackers exploit the gap between what the filter recognises and what the browser executes.
Technique 1 — Case Variation
HTML tag names and attribute names are case-insensitive in HTML. A filter that blocks the lowercase string <script will not match <SCRIPT, <Script>, or <sCrIpT>. The browser treats all of these identically and executes the enclosed code. Case variation is the simplest bypass and is usually attempted first.
Example: <ScRiPt>alert(1)</ScRiPt>
Technique 2 — URL Encoding
When user input is reflected inside a URL context (for example, a redirect parameter or a href attribute), the browser decodes percent-encoded characters before rendering. A filter checking for literal <script will not recognise %3Cscript%3E. Once the browser decodes the URL, the tag is present in full. Double encoding (%253C decoding first to %3C and then to <) is used when the server decodes once before the filter runs.
Example: %3Cscript%3Ealert(1)%3C/script%3E
Technique 3 — HTML Entity Encoding
Inside HTML attribute values and certain text contexts, the browser resolves numeric and named character references before processing. Encoding the opening angle bracket as < or < produces a < character after parsing, even though the filter never saw the literal character. This technique is particularly effective when the injection point is inside an attribute value that is subsequently passed to an innerHTML assignment.
Example: <script>alert(1)</script>
Technique 4 — Event Handler Alternatives
Filters commonly block <script but neglect the full range of HTML event handlers. Every element that can receive a browser event can carry an on* attribute. Common alternatives include:
<img src=x onerror="alert(1)">— fires when the image fails to load<svg onload="alert(1)">— fires when the SVG element is rendered<body onpageshow="alert(1)">— fires on initial render and after cache restoration<input autofocus onfocus="alert(1)">— fires without any user interaction whenautofocusis present<details open ontoggle="alert(1)">— fires immediately in some browsers
These event-handler payloads execute JavaScript without the word script appearing anywhere in the tag.
Technique 5 — Polyglot Payloads
A polyglot is a single payload that is syntactically valid and executable in multiple injection contexts simultaneously — for example, both as an HTML tag and as a valid JavaScript string or SQL fragment. Polyglots are useful when the precise injection context is unknown. A canonical HTML polyglot looks like:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3eThis payload defeats case-sensitive filters, context-guessing filters, and comment-stripping filters simultaneously.
Practical Approach in This Lab
The sandbox in this lesson simulates five discrete WAF rules, each blocking a different class of payload. Your task is to craft one bypass per rule, understand why each succeeds, and then proceed to the flag-capture step. Read each rule description carefully — the precise string or pattern being blocked is always stated.