WAF Bypass
What is a WAF?
A Web Application Firewall sits between the internet and the web application, inspecting HTTP traffic in both directions. It blocks known attack patterns like SQLi, XSS, and command injection based on rules.
How WAFs work:
- Rule-based - predefined patterns. If a request contains SQL syntax, block it
- Heuristics - behavioral analysis over time. 10,000 requests with integer input, then a string input suddenly = suspicious
- Threat intelligence - real-time updates about new attack signatures
The key weakness: WAFs require constant tuning and maintenance. Most organizations deploy them once and forget. This means even when a WAF is present, it is often misconfigured or running outdated rules - weaker than it could be.
Step 1 - Detect the WAF
wafw00f
wafw00f https://TARGET
Identifies the WAF solution if possible (F5, Cloudflare, ModSecurity, Akamai, etc.). If it can’t identify the vendor it still tells you a WAF is present.
If wafw00f can’t identify it, look at the error response manually:
- Send a known-bad payload (e.g.
<script>prompt(1)</script>in any input field) - Normal input → 200 OK
- Bad payload → 403 Forbidden with a custom error page
Custom error pages are often branded by the WAF vendor. The error message or response headers (X-Sucuri-ID, CF-RAY, X-Powered-By-Plesk) can fingerprint it.
Step 2 - Find What Gets Through
Before trying clever bypasses, fuzz to see which payloads actually get blocked. Use Burp Intruder:
- Send a request with a payload parameter to Intruder
- Load an XSS or SQLi wordlist
- Run the attack
- Sort by status code - look for 200s among the 403s
- Those 200s are payloads that slipped through
Important: WAFs block IPs after too many bad requests. Be targeted, not noisy. Use a smaller, curated wordlist rather than throwing thousands of payloads.
Step 3 - Bypass Techniques
Case variation
WAFs match exact patterns. Changing case can bypass naive rules:
sElEcT * fRoM users
UNION SELECT -> UnIoN SeLeCtComment insertion (SQL)
Break up keywords with comments the SQL engine ignores but the WAF doesn’t expect:
SE/**/LECT * FR/**/OM users
UN/**/ION SE/**/LECT null,null,null
/*!UNION*/ /*!SELECT*/ null,nullWhitespace alternatives
Replace spaces with characters the WAF doesn’t account for:
Tab: SELECT%09*%09FROM%09users
Newline: SELECT%0A*%0AFROM%0Ausers
Carriage: SELECT%0D*%0DFROM%0Dusers
Form feed: SELECT%0C*%0CFROM%0Cusers
Encoding
- URL encoding:
<=%3C,>=%3E,'=%27 - Double URL encoding:
%=%25, so<=%253C - HTML encoding:
<=<or< - Base64 + atob(): encode your JS payload in base64, decode and execute it:
<script>eval(atob('YWxlcnQoMSk='))</script>The WAF sees base64 gibberish, the browser decodes and runs it.
Filter bypass for script tag filters
If the WAF strips <script>:
- Switch to event-based payloads:
<img src=x onerror=prompt(1)> - Try:
<svg onload=prompt(1)>,<body onload=prompt(1)> - Test if the filter is recursive:
<scr<script>ipt>prompt(1)</scr</script>ipt>
If the filter removes <script> once but not recursively, the outer tags survive and form the complete payload.
sqlmap tamper scripts
sqlmap has built-in tamper scripts specifically for WAF bypass:
sqlmap -u "URL" --tamper=space2comment
sqlmap -u "URL" --tamper=between
sqlmap -u "URL" --tamper=randomcase
sqlmap -u "URL" --tamper=charencode
sqlmap -u "URL" --tamper=base64encode
sqlmap -u "URL" --tamper=space2comment,randomcase,between
Chain multiple tamper scripts with commas. List all available: sqlmap --list-tampers
HTTP-level bypasses
- Parameter pollution: send the same parameter twice - WAF checks the first, app uses the second
?id=1&id=1 UNION SELECT null--
- Chunked transfer encoding: split the request body into chunks - some WAFs inspect each chunk independently and miss payloads split across chunks
- Content-Type switching: if the app accepts both JSON and form-data, try switching - WAF rules may differ per content type
- HTTP verb tampering: if POST is blocked, try PUT or PATCH
Step 4 - Context Matters
WAF bypasses are situational. What works against one WAF configuration will not work against another. There is no universal bypass. The transcript makes this clear: don’t trust “WAF bypass” posts on social media - test them against the actual target.
When you find a character or keyword being filtered, use the filtering playground approach:
- Identify exactly which characters/keywords are stripped
- Find payloads that achieve the same effect without those characters
- Test encoding variations of the same payload
- Check whether filtering is recursive or applied only once
Quick Reference
| Technique | Use case |
|---|---|
wafw00f | Detect and fingerprint WAF |
| Case variation | Bypass keyword matching |
| Comment insertion | Break SQL keywords |
| Whitespace alternatives | Bypass space-based rules |
| URL / double encoding | Bypass pattern matching |
| Base64 + atob() | Obfuscate JS payloads |
| Event handlers instead of script tags | Bypass script tag filters |
| Recursive filter check | Find non-recursive filter implementations |
sqlmap --tamper | Automated SQLi WAF bypass |
| Parameter pollution | WAF sees one value, app uses another |
Resources
- PayloadsAllTheThings WAF bypass: https://swisskyrepo.github.io/PayloadsAllTheThings/Web%20Application%20Firewall%20Bypass/
- sqlmap tamper scripts list:
sqlmap --list-tampers - wafw00f: https://github.com/EnableSecurity/wafw00f
Testing Checklist
- Detect and fingerprint the WAF: run
wafw00f https://TARGET- it identifies the WAF vendor or at least confirms a WAF is present - Manually confirm the WAF: send a known-bad payload (e.g.,
<script>prompt(1)</script>) and compare the response to a normal input - look for 403 Forbidden, custom error pages, or WAF-branded responses - Check error response headers for WAF fingerprints:
X-Sucuri-ID,CF-RAY,X-Powered-By-Plesk, etc. - Fuzz with a curated wordlist in Burp Intruder to find which payloads get through (200s among the 403s) - use a smaller list to avoid getting your IP blocked
- Test case variation to bypass keyword matching:
sElEcT,UnIoN SeLeCt,ScRiPt - Test comment insertion for SQL:
SE/**/LECT,UN/**/ION SE/**/LECT,/*!UNION*/ /*!SELECT*/ - Test whitespace alternatives: tabs (
%09), newlines (%0A), carriage returns (%0D), form feeds (%0C) instead of spaces - Test encoding bypasses: URL encoding, double URL encoding (
%253C), HTML encoding (<), Base64 witheval(atob('...'))for JS payloads - If
<script>tags are filtered, switch to event-based payloads (<img src=x onerror=prompt(1)>) and test if the filter is recursive (<scr<script>ipt>) - For SQLi behind a WAF, use sqlmap tamper scripts:
--tamper=space2comment,randomcase,between- chain multiple with commas, list all withsqlmap --list-tampers - Test HTTP-level bypasses: parameter pollution (same param twice), Content-Type switching (form-data vs JSON), HTTP verb tampering (POST to PUT/PATCH)
- Remember: WAF bypasses are situational. What works against one configuration will not work against another. Identify exactly which characters/keywords are blocked, then craft payloads that achieve the same effect without them
- Include in report: the WAF identified, the blocking behavior observed, the bypass technique and payload used, and the resulting vulnerability (SQLi, XSS, etc.) that was exploited through the bypass