I have written this short blog as a teaching source and quick reference about LFI in C#.
What is Local File Inclusion (LFI)?
According to the OWASP Web Testing Guide (WSTG), Local File Inclusion (LFI) vulnerability allows an attacker to read files that they shouldn’t have access to. The vulnerability occurs due to the use of user-supplied input without proper validation. This vulnerability can lead to but is not limited to:
- Code execution on the web server
- Code execution on the client side such as JavaScript can lead to other attacks such as cross-site scripting (XSS)
- Denial of Service (DoS)
- Sensitive Information Disclosure
How to Detect LFI
To detect LFI we look for user-controlled inputs that go directly to file-manipulating functions in C#. An easy way to look for these is to look for inputs that take filenames, paths and or URLs or part thereof. Here are some examples of vulnerable LFI code (Bing-generated examples):
// Example 1: Direct inclusion of user input in file path
string userFile = Request.QueryString["file"];
string filePath = Server.MapPath("~/uploads/" + userFile);
System.IO.StreamReader fileReader = new System.IO.StreamReader(filePath);
string text = fileReader.ReadToEnd();
fileReader.Close();
In Example 1, we have no input sanitisation, which means all user inputs are passed directly to the System.IO.StreamReader function, so a path like ../../../../../../../../windows/win.ini would return the win.ini file.
// Example 2: Insufficient sanitisation of user input
string userFile = Request.QueryString["file"];
string sanitizedFile = userFile.Replace("../", ""); // This is not enough!
string filePath = Server.MapPath("~/uploads/" + sanitizedFile);
System.IO.StreamReader fileReader = new System.IO.StreamReader(filePath);
string text = fileReader.ReadToEnd();
fileReader.Close();
In Example 2, we have an attempt at input sanitisation, but this would stop only one of the many ways you can request files. For example, ..\..\..\..\..\..\..\..\..\windows\win.ini is another method that can be used to extract files from this function. Please note there are multiple different LFI attack techniques.
// Example 3: Direct inclusion of user input in URL without checking for malicious content
string userURL = Request.QueryString["url"];
WebClient client = new WebClient();
string data = client.DownloadString(userURL);
In Example 3, we have a URL being injected, it would work similarly to Example 1, in this case, something like file://windows/win.ini would extract the win.ini file. It also adds the risk of a Server-Side Request Forgery (SSRF) vulnerability, but we will cover this in another blog in the future. Again, there are multiple different attack vectors to extract local or remote files.
How to Fix LFI
The best approach to solve this type of vulnerability is:
- Elimination. To not pass user-controlled inputs to file-manipulating functions.
- Whitelisting. If elimination is not possible, use a list of allowed inputs to identify valid inputs.
- Blacklisting. If whitelist sanitisation is not possible, use a list of inputs that are not allowed. This method is not as effective as the first two recommendations.
- As a minimum always use the in-built sanitisation tooling.
Here is a list of C# in-built sanitisation functions with a short description for each function:
- System.IO.Path.GetInvalidFileNameChars() — Returns an array containing the characters that are not allowed in file names1.
- Encoder.HtmlEncode(string) — Encodes a string to be displayed in a browser. This can prevent cross-site scripting (XSS) attacks.
- double.TryParse(string, out double) — Tries to convert the string representation of a number to its double-precision floating-point number equivalent. This can be used to sanitise numeric input.
- SqlCommand.Parameters.AddWithValue(string, object) — Adds a new parameter to a SqlCommand, given the parameter name and value. This can prevent SQL injection attacks.
- HtmlSanitizer.Sanitize(string) — Sanitises a string that contains HTML4. This can prevent XSS attacks.
- Microsoft.Security.Application.Encoder.LdapFilterEncode(string): Encodes input for use in LDAP filters.
- Microsoft.Security.Application.Encoder.LdapDistinguishedNameEncode(string): Encodes input for use in LDAP distinguished names.
- Microsoft.Security.Application.Encoder.XmlEncode(string): Encodes input for use in XML data.
- Microsoft.Security.Application.Encoder.UrlEncode(string): Encodes input for use in URL data.
- Microsoft.Security.Application.Encoder.UrlPathEncode(string): Encodes input for use in URL paths.
Disclaimer: I used Bing Chat to assist with the production of examples, grammar and spelling. Acknowledgements have been provided as links to the information I researched for this blog.