Many modern back-end languages, such as PHP, Javascript, or Java, use HTTP parameters to specify what is shown on the web page, which allows for building dynamic web pages, reduces the script’s overall size, and simplifies the code. In such cases, parameters are used to specify which resource is shown on the page. If such functionalities are not securely coded, an attacker may manipulate these parameters to display the content of any local file on the hosting server, leading to a Local File Inclusion (LFI) vulnerability.
Local File Inclusion (LFI)
The most common place we usually find LFI within is templating engines. In order to have most of the web application looking the same when navigating between pages, a templating engine displays a page that shows the common static parts, such as the header, navigation bar, and footer, and then dynamically loads other content that changes between pages. Otherwise, every page on the server would need to be modified when changes are made to any of the static parts. This is why we often see a parameter like /index.php?page=about, where index.php sets static content (e.g. header/footer), and then only pulls the dynamic content specified in the parameter, which in this case may be read from a file called about.php. As we have control over the about portion of the request, it may be possible to have the web application grab other files and display them on the page.
LFI vulnerabilities can lead to source code disclosure, sensitive data exposure, and even remote code execution under certain conditions. Leaking source code may allow attackers to test the code for other vulnerabilities, which may reveal previously unknown vulnerabilities. Furthermore, leaking sensitive data may enable attackers to enumerate the remote server for other weaknesses or even leak credentials and keys that may allow them to access the remote server directly. Under specific conditions, LFI may also allow attackers to execute code on the remote server, which may compromise the entire back-end server and any other servers connected to it.
Examples of Vulnerable Code
PHP
In PHP, we may use the include() function to load a local or a remote file as we load a page. If the path passed to the include() is taken from a user-controlled parameter, like a GET parameter, and the code does not explicitly filter and sanitize the user input, then the code becomes vulnerable to File Inclusion. The following code snippet shows an example of that:
if (isset($_GET['language'])) {
include($_GET['language']);
}Such functions include include_once(), require(), require_once(), file_get_contents(), and several others as well.
NodeJS
if(req.query.language) {
fs.readFile(path.join(__dirname, req.query.language), function (err, data) {
res.write(data);
});
}As we can see, whatever parameter passed from the URL gets used by the readfile function, which then writes the file content in the HTTP response. Another example is the render() function in the Express.js framework. The following example shows how the language parameter is used to determine which directory to pull the about.html page from:
Unlike our earlier examples where GET parameters were specified after a (?) character in the URL, the above example takes the parameter from the URL path (e.g. /about/en or /about/es). As the parameter is directly used within the render() function to specify the rendered file, we can change the URL to show a different file instead.
Java
The same concept applies to many other web servers. The following examples show how web applications for a Java web server may include local files based on the specified parameter, using the include function:
<c:if test="${not empty param.language}">
<jsp:include file="<%= request.getParameter('language') %>" />
</c:if><c:import url= "<%= request.getParameter('language') %>"/>.NET
@if (!string.IsNullOrEmpty(HttpContext.Request.Query['language'])) {
<% Response.WriteFile("<% HttpContext.Request.Query['language'] %>"); %>
}Furthermore, the @Html.Partial() function may also be used to render the specified file as part of the front-end template, similarly to what we saw earlier:
@Html.Partial(HttpContext.Request.Query['language'])<!--#include file="<% HttpContext.Request.Query['language'] %>"-->Read vs Execute
The most important thing to keep in mind is that some of the above functions only read the content of the specified files, while others also execute the specified files.
Furthermore, some of them allow specifying remote URLs, while others only work with files local to the back-end server.
| Function | Read Content | Execute | Remote URL |
|---|---|---|---|
| PHP | |||
include()/include_once() | ✅ | ✅ | ✅ |
require()/require_once() | ✅ | ✅ | ❌ |
file_get_contents() | ✅ | ❌ | ✅ |
fopen()/file() | ✅ | ❌ | ❌ |
| NodeJS | |||
fs.readFile() | ✅ | ❌ | ❌ |
fs.sendFile() | ✅ | ❌ | ❌ |
res.render() | ✅ | ✅ | ❌ |
| Java | |||
include | ✅ | ❌ | ❌ |
import | ✅ | ✅ | ✅ |
| .NET | |||
@Html.Partial() | ✅ | ❌ | ❌ |
@Html.RemotePartial() | ✅ | ❌ | ✅ |
Response.WriteFile() | ✅ | ❌ | ❌ |
include | ✅ | ✅ | ✅ |