File Inclusion LFI / RFI
PHP
If the path
passed to the include()
is taken from a user-controlled parameter, like a GET
parameter, and the code does not explicitly filter and sanitize the user input
, then the code becomes vulnerable to File Inclusion
PHP functions that would lead to the same vulnerability if we had control over the path passed into them: include_once()
, require()
, require_once()
, file_get_contents()
NodeJS
readfile():
render() in Express.js:
Java
include
import
.NET
Response.WriteFile
@Html.Partial()
include
Read vs Execute
Function | Read Content | Execute | Remote URL |
PHP | |||
| ✅ | ✅ | ✅ |
| ✅ | ✅ | ❌ |
| ✅ | ❌ | ✅ |
| ✅ | ❌ | ❌ |
NodeJS | |||
| ✅ | ❌ | ❌ |
| ✅ | ❌ | ❌ |
| ✅ | ✅ | ❌ |
Java | |||
| ✅ | ❌ | ❌ |
| ✅ | ✅ | ✅ |
.NET | |||
| ✅ | ❌ | ❌ |
| ✅ | ❌ | ✅ |
| ✅ | ❌ | ❌ |
| ✅ | ✅ | ✅ |
Local File Inclusion (LFI)
Detection - Error Message
Warning: main(pages/$page): failed to open stream: No such file or directory in /home/enigmagroup/public_html/challenges/basics/vm/1/index.php on line 14
Basic LFI
/etc/passwd is forbidden - Try !/etc!/passwd
Path Traversal
Fuzzing
FuzzingPayloads
NGINX Alias Path Traversal
Burp extension
Filename Prefix
prefix a /
before our payload, and this should consider the prefix as a directory
Note: This may not always work, as in this example a directory named lang_/
may not exist, so our relative path may not be correct. Furthermore, any prefix appended to our input may break some file inclusion techniques
we will discuss in upcoming sections, like using PHP wrappers and filters or RFI.
Appended Extensions
See Basic Bypass - Appended Extension
Second-Order Attacks
For example, a web application may allow us to download our avatar through a URL like (/profile/$username/avatar.png
). If we craft a malicious LFI username (e.g. ../../../etc/passwd
), then it may be possible to change the file being pulled to another local file on the server and grab it instead of our avatar.
Django, Rails, or Node.js Web Application Header Values
Windows LFI
Payload List
Basic Bypasses
/etc/passwd is forbidden - Try !/etc!/passwd
Non-Recursive Path Traversal Filters
....//
Other bypass:
..././
escaping the forward slash character:
....\/
adding extra forward slashes:
....////
Encoding
Core PHP filters on versions 5.3.4 and earlier were specifically vulnerable to this bypass, but even on newer versions we may find custom filters that may be bypassed through URL encoding
Encode ../
into %2e%2e%2f
Note: For this to work we must URL encode all characters, including the dots. Some URL encoders may not encode dots as they are considered to be part of the URL scheme.
Furthermore, we may also use Burp Decoder to encode the encoded string once again to have a double encoded
string, which may also bypass other types of filters.
You may refer to the Command Injections module for more about bypassing various blacklisted characters, as the same techniques may be used with LFI as well.
Command InjectionApproved Paths
To find the approved path, we can examine the requests sent by the existing forms, and see what path they use for the normal web functionality. Furthermore, we can fuzz web directories under the same path, and try different ones until we get a match. To bypass this, we may use path traversal and start our payload with the approved path
Appended Extension
With modern versions of PHP, we may not be able to bypass this and will be restricted to only reading files in that extension, which may still be useful, as we will see in the next section (e.g. for reading source code).
There are a couple of other techniques we may use, but they are obsolete with modern versions of PHP and only work with PHP versions before 5.3/5.4
.
Path Truncation
In earlier versions of PHP, defined strings have a maximum length of 4096 characters, likely due to the limitation of 32-bit systems. If a longer string is passed, it will simply be truncated
, and any characters after the maximum length will be ignored. Furthermore, PHP also used to remove trailing slashes and single dots in path names, so if we call (/etc/passwd/.
) then the /.
would also be truncated, and PHP would call (/etc/passwd
). PHP, and Linux systems in general, also disregard multiple slashes in the path (e.g. ////etc/passwd
is the same as /etc/passwd
). Similarly, a current directory shortcut (.
) in the middle of the path would also be disregarded (e.g. /etc/./passwd
).
It is also important to note that we would also need to start the path with a non-existing directory
for this technique to work.
Null Bytes
Adding a null byte (%00
) at the end of the string would terminate the string and not consider anything after it.
/etc/passwd%00
=> /etc/passwd%00.php
PHP Filters
Input Filters
php://filter/
The filter that is useful for LFI attacks is the convert.base64-encode
filter
Fuzzing for PHP Files
Tip: Unlike normal web application usage, we are not restricted to pages with HTTP response code 200, as we have local file inclusion access, so we should be scanning for all codes, including 301
, 302
and 403
pages, and we should be able to read their source code as well.
Start by reading index.php
and scanning it for more references and so on, but fuzzing for PHP files may reveal some files that may not otherwise be found that way.
Source Code Disclosure
Note: We intentionally left the resource file at the end of our string, as the .php
extension is automatically appended to the end of our input string, which would make the resource we specified be config.php
.
Tip: When copying the base64 encoded string, be sure to copy the entire string or it will not fully decode. You can view the page source to ensure you copy the entire string.
Error-based oracle - Dump File
PHP Wrappers
Data
The data wrapper can be used to include external data, including PHP code. However, the data wrapper is only available to use if the (allow_url_include
) setting is enabled in the PHP configurations.
This option is not enabled by default
, and is required for several other LFI attacks, like using the input
wrapper or for any RFI attack
PHP Configurations
/etc/php/X.Y/apache2/php.ini
for Apache /etc/php/X.Y/fpm/php.ini
) for Nginx
where X.Y
is your install PHP version
Remote Code Execution
We can URL encode the base64 string, and then pass it to the data wrapper with data://text/plain;base64
Input
We pass our input to the input
wrapper as a POST request's data. . So, the vulnerable parameter must accept POST requests for this attack to work
Note: To pass our command as a GET request, we need the vulnerable function to also accept GET request (i.e. use $_REQUEST
). If it only accepts POST requests, then we can put our command directly in our PHP code, instead of a dynamic web shell (e.g. <\?php system('id')?>
)
Expect
Don't need to provide a web shell, as it is designed to execute commands.
Determine whether Expect is installed on the back-end server - See PHP Configurations
Use expect://
wrapper
expect
module also use for XXE vulnerabilities
Cookie Based PHP LFI
Remote File Inclusion (RFI)
"Remote File Inclusion (RFI)", if the vulnerable function allows the inclusion of remote URLs. This allows two main benefits:
Enumerating local-only ports and web applications (i.e. SSRF)
Gaining remote code execution by including a malicious script that we host
The Server-side Attacks module (Bug Bounty Hunter Path) covers various SSRF
techniques, which may also be used with RFI vulnerabilities.
LFI may not necessarily be an RFI - See Read vs. Execute
Verify RFI
This may not always be reliable, as even if this setting is enabled, the vulnerable function may not allow remote URL inclusion to begin with.
A more reliable way to determine whether an LFI vulnerability is also vulnerable to RFI is to try and include a URL
, and see if we can get its content
We should always start by trying to include a local URL
Note: It may not be ideal to include the vulnerable page itself (i.e. index.php), as this may cause a recursive inclusion loop and cause a DoS to the back-end server.
Remote Code Execution with RFI
We can use a custom web shell we download from the internet, use a reverse shell script, or write our own basic web shell
Web Shell It is a good idea to listen on a common HTTP port like 80
or 443
, as these ports may be whitelisted in case the vulnerable web application has a firewall preventing outgoing connections. Furthermore, we may host the script through an FTP service or an SMB service
HTTP
Tip: We can examine the connection on our machine to ensure the request is being sent as we specified it. For example, if we saw an extra extension (.php) was appended to the request, then we can omit it from our payload
FTP
If the server requires valid authentication, then the credentials can be specified in the URL, as follows:
SMB
If the vulnerable web application is hosted on a Windows server (which we can tell from the server version in the HTTP response headers), then we do not need the allow_url_include
setting to be enabled for RFI exploitation, as we can utilize the SMB protocol for the remote file inclusion.
This technique is more likely to work if we were on the same network
, as accessing remote SMB servers over the internet may be disabled by default, depending on the Windows server configurations
LFI and File Uploads
File Upload AttacksCrafting Malicious Image
Note: We are using a GIF
image in this case since its magic bytes are easily typed, as they are ASCII characters, while other extensions have magic bytes in binary that we would need to URL encode. However, this attack would work with any allowed image or file type.
We need to know the path to our uploaded file - Source code analysis
Note: As we can see, we can use /profile_images/shell.gif
for the file path. If we do not know where the file is uploaded, then we can fuzz for an uploads directory, and then fuzz for our uploaded file, though this may not always work as some web applications properly hide the uploaded files.
Note: To include to our uploaded file, we used ./profile_images/
as in this case the LFI vulnerability does not prefix any directories before our input. In case it did prefix a directory before our input, then we simply need to ../
out of that directory and then use our URL path, as we learned in previous sections.
Zip Upload
Note: Even though we named our zip archive as (shell.jpg), some upload forms may still detect our file as a zip archive through content-type tests and disallow its upload, so this attack has a higher chance of working if the upload of zip archives is allowed.
Note: We added the uploads directory (./profile_images/
) before the file name, as the vulnerable page (index.php
) is in the main directory.
Phar Upload
shell.php:
Compiled into a phar file:
LFI2RCE via phpinfo()
LFI/uploads which occurs if file uploads is enabled in the PHP configurations and the phpinfo()
page is somehow exposed to us
Log Poisoning
For this attack to work, the PHP web application should have read privileges over the logged files, which vary from one server to another.
PHP Session Poisoning
/var/lib/php/sessions/
on Linux and in C:\Windows\Temp\
on Windows
If the PHPSESSID
cookie is set to el4ukv0kqbvoirg7nkp4dncpk3
, then its location on disk would be /var/lib/php/sessions/sess_el4ukv0kqbvoirg7nkp4dncpk3
Use the cookie value you find in your own session
Include the session file once again
Writing PHP code to the session file
Include the session file and use the &cmd=id
Note: To execute another command, the session file has to be poisoned with the web shell again, as it gets overwritten with /var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd
after our last inclusion. Ideally, we would use the poisoned web shell to write a permanent web shell to the web directory, or send a reverse shell for easier interaction.
Server Log Poisoning
Apache
logs are located in /var/log/apache2/
on Linux and in C:\xampp\apache\logs\
on Windows, while Nginx
logs are located in /var/log/nginx/
on Linux and in C:\nginx\log\
on Windows
The logs may be in a different location in some cases => Fuzz
User-Agent
header is controlled by us through the HTTP request headers, so we should be able to poison this value.
Tip: Logs tend to be huge, and loading them in an LFI vulnerability may take a while to load, or even crash the server in worst-case scenarios. So, be careful and efficient with them in a production environment, and don't send unnecessary requests.
Note: As all requests to the server get logged, we can poison any request to the web application, and not necessarily the LFI one as we did above.
or with Curl
Using Burp is safer, Curl can overwrite the log file
We can specify a command to be executed with (?cmd=id
)
Tip: The User-Agent
header is also shown on process files under the Linux /proc/
directory. So, we can try including the /proc/self/environ
or /proc/self/fd/N
files (where N is a PID usually between 0-50), and we may be able to perform the same attack on these files. This may become handy in case we did not have read access over the server logs, however, these files may only be readable by privileged users as well.
Service logs we may be able to read:
/var/log/sshd.log
/var/log/mail
/var/log/vsftpd.log
Automated Scanning
Fuzzing parameters
FuzzingFuzz for exposed parameters
Tip: For a more precise scan, we can limit our scan to the most popular LFI parameters
LFI wordlists
Start with LFI-Jhaddix.txt: https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/LFI/LFI-Jhaddix.txt
Fuzzing Server Files
Server Webroot
Linux:
Windows
Depending on our LFI situation, we may need to add a few back directories (e.g. ../../../../
), and then add our index.php
afterwords.
Also try LFI-Jhaddix.txt
Server Logs/Configurations
Start with LFI-Jhaddix.txt
More payload:
Linux
Windows
We do get the default webroot path and the log path. However, in this case, the log path is using a global apache variable (APACHE_LOG_DIR
), which are found in another file we saw above, which is (/etc/apache2/envvars
)
The (APACHE_LOG_DIR
) variable is set to (/var/log/apache2
), and the previous configuration told us that the log files are /access.log
and /error.log
Tools
Resources
Last updated