Testing for File Inclusion
The File Inclusion vulnerability allows an attacker to include a file, usually exploiting a “dynamic file inclusion” mechanism implemented in the target application. The vulnerability occurs due to the use of user-supplied input without proper validation.
This can lead to something as simple as outputting the contents of the file, but it can also lead to:
- Code execution on the web server
- Denial of Service (DoS)
- Sensitive Information Disclosure
Local File Inclusion (LFI) is the process of including files that are already present on the server through exploitation of vulnerable inclusion procedures implemented in the application. For example, this vulnerability occurs when a page receives input that is a path to a local file. This input is not properly sanitized, allowing directory traversal characters to be injected (such as
../ – see 4.5.1 Testing Directory Traversal File Include).
Remote File Inclusion (RFI) is the process of including files from remote sources through exploitation of vulnerable inclusion procedures implemented in the application. For example, this vulnerability occurs when a page receives input that is the URL to a remote file. This input is not properly sanitized, allowing external URLs to be injected.
In both cases, although most examples point to vulnerable PHP scripts, we should keep in mind that it is also common in other technologies such as JSP, ASP, etc.
- Identify file inclusion points.
- Assess the severity or potential impact of the vulnerabilities.
How to Test
Testing for Local File Inclusion
Since LFI occurs when paths passed to
include statements are not properly sanitized, in a black-box testing approach, we should look for functionality that accepts file names/paths as parameters.
Consider the following example:
This looks to be a promising place to try LFI. If the application does not select the appropriate page given in the
file parameter and instead directly includes the input, it is possible to include arbitrary files from the server.
A typical proof-of-concept exploit would be to attempt to load the
passwd file with:
If the above mentioned conditions are met, an attacker would see something like the following included in the response:
root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin alex:x:500:500:alex:/home/alex:/bin/bash margo:x:501:501::/home/margo:/bin/bash ...
Even when such a vulnerability exists, its exploitation could be more complex in real life scenarios. Consider the following piece of code:
<?php include($_GET['file'].".php"); ?>
Simple substitution with a random filename would not work as the postfix
.php is appended to the provided input. In order to bypass it, a tester can use several techniques to get the expected exploitation.
Null Byte Injection
null character (also known as
null terminator or
null byte) is a control character with the value zero present in many character sets that is being used as a reserved character to mark the end of a string. Once used, any character after this special byte will be ignored. Commonly the way to inject this character would be with the URL encoded string
%00 by appending it to the requested path. In our previous sample, performing a request to
http://vulnerable_host/preview.php?file=../../../../etc/passwd%00 would ignore the
.php extension being added to the input filename, returning to an attacker a list of basic users as a result of a successful exploitation.
Path and Dot Truncation
Most PHP installations have a filename limit of 4096 bytes. If any given filename is longer than that length, PHP simply truncates it, discarding any additional characters. Abusing this behavior makes it possible to make the PHP engine ignore the
.php extension by moving it out of the 4096 bytes limit. When this happens, no error is triggered; the additional characters are simply dropped and PHP continues its execution normally.
This bypass would commonly be combined with other logic bypass strategies such as encoding part of the file path with Unicode encoding, the introduction of double encoding, or any other input that would still represent the valid desired filename.
Local File Inclusion vulnerabilities are commonly seen as read only vulnerabilities that an attacker can use to read sensitive data from the server hosting the vulnerable application. However, in some specific implementations this vulnerability can be used to upgrade the attack from LFI to Remote Code Execution vulnerabilities that could potentially fully compromise the host.
This enhancement is common when an attacker could be able to combine the LFI vulnerability with certain PHP wrappers.
A wrapper is a code that surrounds other code to perform some added functionality. PHP implements many built-in wrappers to be used with file system functions. Once their usage is detected during the testing process of an application, it’s a good practice to try to abuse it to identify the real risk of the detected weakness(es). Below is a list with the most commonly used wrappers, even though you should consider that it is not exhaustive and at the same time it is possible to register custom wrappers that if employed by the target, would require a deeper adhoc analysis.
Used to access the local file system; this is a case insensitive wrapper that provides the capability to apply filters to a stream at the time of opening a file. This wrapper can be used to get content of a file preventing the server from executing it. For example, allowing an attacker to read the content of PHP files to get source code to identify sensitive information such as credentials or other exploitable vulnerabilities.
The wrapper can be used like
FILE is the file to retrieve. As a result of the usage of this execution, the content of the target file would be read, encoded to base64 (this is the step that prevents the execution server-side), and returned to the User-Agent.
In PHP 7.2.0, the
zip:// wrapper was introduced to manipulate
zip compressed files. This wrapper expects the following parameter structure:
filename_path is the path to the malicious zip archive and
internal_filename is the path of the malicious file placed inside the processed ZIP file. During the exploitation, it’s common that the
# would be encoded with its URL encoded value
Abuse of this wrapper could allow an attacker to design a malicious ZIP file that could be uploaded to the server, for example as an avatar image or using any file upload system available on the target website (the
php:zip:// wrapper does not require the zip file to have any specific extension) to be executed by the LFI vulnerability.
In order to test this vulnerability, the following procedure could be followed to attack the previous code example provided.
- Create the PHP file to be executed, for example with the content
<?php phpinfo(); ?>and save it as
- Compress it as a new ZIP file called
- Rename the
target.jpgto bypass the extension validation and upload it to the target website as your avatar image.
- Supposing that the
target.jpgfile is stored locally on the server to the
../avatar/target.jpgpath, exploit the vulnerability with the PHP ZIP wrapper by injecting the following payload to the vulnerable URL:
Since on our sample the
.php extension is concatenated to our payload, the request to
http://vulnerable_host/preview.php?file=zip://../avatar/target.jpg%23code will result in the execution of the
code.php file existing in the malicious ZIP file.
Available since PHP 5.2.0, this wrapper expects the following usage:
BASE64_STR is expected to be the Base64 encoded content of the file to be processed. It’s important to consider that this wrapper would only be available if the option
allow_url_include would be enabled.
In order to test for LFI using this wrapper, the code to be executed should be Base64 encoded. For example,
<?php phpinfo(); ?> would be encoded as:
PD9waHAgcGhwaW5mbygpOyA/Pg== and the payload would be represented as:
This wrapper, which is not enabled by default, provides access to processes
stderr. Given in the format
expect://command, the server would execute the provided command using
BASH and return the result.
Testing for Remote File Inclusion
Since RFI occurs when URLs passed to
include statements are not properly sanitized, in a black-box testing approach, we should look for scripts that take filenames as parameters. Consider the following PHP example:
$incfile = $_REQUEST["file"]; include($incfile.".php");
In this example the path is extracted from the HTTP request and no input validation is done (for example, by checking the input against an allow list), so this snippet of code is vulnerable to this type of attack. Consider the following URL:
In this case the remote file is going to be included and any code contained in it is going to be run by the server.
The most effective solution to eliminate file inclusion vulnerabilities is to avoid passing user-submitted input to any filesystem/framework API. If this is not possible, the application can maintain an allow list of files that may be included by the page, and then use an identifier (for example, the index number) to access the selected file. Any request containing an invalid identifier should be rejected so that there is no opportunity for malicious users to manipulate the path. Check out the File Upload Cheat Sheet for good security practices on this topic.