Local file inclusion - Python

Local file inclusion - Python

Need

Prevention of unauthorized access to sensitive files on the server

Context

  • Usage of Python 3 for writing and executing Python code
  • Usage of Django for building web applications

Description

Non compliant code

        from django.http import HttpResponse
from django.views import View
import os

class FileView(View):
    def get(self, request, *args, **kwargs):
        filename = request.GET.get('file')
        file_path = os.path.join('/app/files/', filename)
        with open(file_path, 'r') as file:
            data = file.read()
        return HttpResponse(data)
        
        

The above code is a simple Django view that reads a file from the server's local filesystem and returns its contents in the HTTP response. The filename is taken directly from the 'file' GET parameter in the request URL.

The vulnerability here is Local File Inclusion (LFI), which occurs when an application uses user-supplied input to construct a pathname that is intended to identify a file on the server.

In the above code, the 'file' GET parameter from the request URL is used directly to construct the file path. This means that an attacker could potentially manipulate the 'file' parameter to read any file on the server's filesystem that the Django application has read access to. For example, an attacker could use relative paths ( ../) to move up in the directory structure and access sensitive files.

This vulnerability is serious because it could allow an attacker to read sensitive information from the server, such as configuration files, source code, or data files. In some cases, if the server is configured to execute certain file types and the attacker can control the file's content, this vulnerability could even lead to Remote Code Execution (RCE).

Steps

  • Validate the input: Ensure that the filename provided by the user is valid and does not contain any special characters or path sequences (like '../') that could be used to traverse directories.
  • Limit the accessible files: Restrict the files that can be accessed to a specific directory and do not allow access to files outside of this directory.
  • Use a whitelist approach: Only allow access to specific known files, rather than allowing any file to be accessed.
  • Handle errors properly: If a file cannot be found or opened, handle this error in a way that does not expose any sensitive information.

Compliant code

        from django.http import HttpResponse, Http404
from django.views import View
import os
import re

class FileView(View):
    def get(self, request, *args, **kwargs):
        filename = request.GET.get('file')

        # Validate the filename to ensure it does not contain any special characters or path sequences
        if not re.match(r'^[\\w.]+$', filename) or '..' in filename or filename.startswith('/'):
            raise Http404("Invalid file name")

        file_path = os.path.join('/app/files/', filename)

        # Ensure the file is within the specified directory
        if not os.path.realpath(file_path).startswith('/app/files/'):
            raise Http404("File not found")

        try:
            with open(file_path, 'r') as file:
                data = file.read()
        except IOError:
            raise Http404("File not found")

        return HttpResponse(data)
        
        

The updated code includes several measures to prevent local file inclusion vulnerabilities:

1. Input Validation: The filename provided by the user is validated using a regular expression to ensure it only contains alphanumeric characters, underscores, and periods. This prevents the use of special characters or path sequences (like '../') that could be used to traverse directories.

2. Path Restriction: The os.path.realpath() function is used to get the absolute path of the file. This is then checked to ensure it is within the '/app/files/' directory. If the file is outside of this directory, a 404 error is returned.

3. Error Handling: If the file cannot be found or opened (for example, if it does not exist or the server does not have the necessary permissions), an IOError will be raised. This is caught and handled by returning a 404 error, preventing any sensitive information from being exposed.

References