When we visit a website, it is common to be able to browse different pages. Each page can be represented by a file on the server. In order to determine which file to provide, the application needs the client to specify which file it is interested in. To do this, sometimes the page the user wants is present in a request parameter.

For example, a website could have the following pages:

  • https://example.com/index.php?page=about.html for useful information.
  • https://example.com/index.php?page=contact.html to access a contact form.
  • https://example.com/index.php?page=articles.html to browse the articles.

As we can see, the ‘page’ parameter plays an essential role in controlling which file is displayed. As a result, it is quite legitimate to wonder what the consequences might be of changing this value to another. And this is precisely what an attacker would attempt, known as “LFI” for “Local File Inclusion”.

What is an LFI (Local File Inclusion) vulnerability?

An LFI vulnerability involves exploiting a feature offered by the application to include another file located on the system running the application. In some cases, this vulnerability can be combined with an attack called “Path Traversal”, which aims to navigate to parent directories in order to include any file on the system.

Depending on how the file is included, an attacker can exploit this vulnerability to execute code on the server, particularly if the contents of the file are interpreted.

He can also trick other users if he manages to control the content of a file on the server, for example using an XSS (Cross-Site Scripting) attack. In addition, he can simply obtain sensitive information, such as the application’s source code or log files.

Exploiting an LFI vulnerability using a log file

As an attacker, we will then try to control this parameter and observe how the application reacts. For example, instead of reading the “about.html” file, we will try to read “./about.html”.

In some cases, the application may detect the presence of special characters and prevent the request, which would limit what we could do. Fortunately for us this is not the case and the application still works.

We’re going to try to access ‘/etc/passwd’, as this file is generally present on Linux servers and is readable by everyone by default, making it a good candidate for our tests.

Unfortunately this doesn’t work. We can assume that the application prefixes our parameter with the name of a folder, for example “pages”, which means that the application would try to read the file “pages/etc/passwd” which doesn’t exist.

However, we try another approach to successively go back up the parent folders to the root to read the file we’re interested in. We use “../../../../../../etc/passwd” as the parameter, and this time it works.

https://example.com/index.php?page=../../../../../../etc/passwd then displays the contents of the /etc/passwd file.

On several occasions, we have encountered applications with this vulnerability, in addition to other features such as user management and authentication.

In particular, these platforms allowed users to reset their account password. And when a user performs a password reset, the application sends an email link to the user with the following form:

https://example.com/reset.php?token=n61jqwfkzwn99qjerjvwz0dn0tmn4bogu8sp5mmm

The problem is that when the user clicks on this link, the server records the request in a log file. If an attacker manages to access this file, he could then reuse this token to reinitialise the user’s account before the user even has a chance to do so.

The log file is generally located in /var/log/nginx/access.log and if we decide to consult it, we may observe something similar to the following:

172.17.0.1 - - [07/Apr/2023:13:58:59 +0000] "GET /reset.php?token=n61jqwfkzwn99qjerjvwz0dn0tmn4bogu8sp5mmm HTTP/1.1" 200 0 "-" "curl/7.85.0" "-"

All an attacker has to do is automate access to this file, using the vulnerability to steal an account as soon as a new line appears.

How to prevent LFI vulnerabilities?

There are various solutions for protecting against an LFI vulnerability. If you know all the pages on your website in advance and don’t mind keeping a list of authorised pages, you might consider checking whether the parameter allowing access to the file belongs to this list.

If your files are not located in sub-folders, you could consider using a function to extract part of the parameter, such as the “basename” function in PHP, for example. Another approach is simply to forbid the presence of a slash (“/”) in the parameter.

However, if you want to use sub-folders, the solution becomes more complex. One approach would be to determine the real path of the file on the disk using a function like “realpath” in PHP, and then check whether this path actually starts with the folder where all your pages are located. This would ensure that the files included actually come from the authorised directory, while allowing sub-folders to be used securely.

In principle, applying one of these solutions should completely eliminate the vulnerability. But it is possible to go even further by not having any secrets in the log files.

It is possible to put the secret in a part of the URL that will not be sent to the server, by adding it after “#” at the end of the URL.

https://example.com/reset.php#token=n61jqwfkzwn99qjerjvwz0dn0tmn4bogu8sp5mmm

However, with this method, the server no longer receives the token directly. To send it to the server, you need to implement client-side logic with JavaScript to retrieve this part of the URL and send the token in the body of a request (POST type for example). In this way, the token will not be present in the server’s log file.

Another option would be to configure your server in such a way as to modify what is recorded in the log files, either per specific route or for the whole server.

Author : Arnaud PASCAL – Pentester @Vaadata