SQL Injection - Headers - Python

SQL Injection - Headers - Python

Need

Prevention of SQL injection in the idClient and application headers

Context

  • Usage of Python 3.0+ for developing Python applications
  • Usage of Django for building web applications in Python

Description

Non compliant code

        from django.http import HttpResponse
from django.db import connection

def get_client(request):
    idClient = request.META['HTTP_IDCLIENT']
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM clients WHERE id = %s", [idClient])
    client = cursor.fetchone()
    return HttpResponse(client)
        
        

The above code is a Django view that gets a client's information from the database. The idClient is taken from the HTTP headers of the request, specifically the idClient header. This value is then directly used in a SQL query to fetch the client’s data from the database.

The vulnerability lies in the line:

                cursor.execute("SELECT * FROM clients WHERE id = %s", [idClient])

            


This line of code is vulnerable to SQL Injection. The application includes the idClient header value directly into the SQL query without any form of sanitization or parameterization. An attacker could modify the idClient header to include SQL code, which would then be executed by the server. This could lead to unauthorized access to data, data manipulation, data loss, or even denial of service.

For example, an attacker could set the idClient header to 1 OR 1=1, which would return all clients, not just the one with the specified ID. Or they could use 1; DROP TABLE clients; to delete the entire clients table.

To exploit this vulnerability, an attacker would only need to modify the HTTP headers, which can be done using many common tools or even just a web browser with developer tools enabled.

Steps

  • Use Django's built-in ORM to interact with the database instead of raw SQL queries. This will automatically escape any SQL special characters, preventing SQL injection.
  • Validate the 'idClient' header to ensure it only contains expected values. For example, if 'idClient' should be an integer, convert the value to an integer and handle any exceptions that occur.
  • Use parameterized queries or prepared statements instead of string formatting to include user input in SQL queries. This ensures that user input is always treated as data, not as part of the SQL command.
  • Limit the permissions of the database user used by the application. This user should only have the minimum permissions necessary to perform its tasks. This way, even if an attacker manages to inject SQL, they won't be able to do much damage.

Compliant code

        from django.http import HttpResponse
from django.core.exceptions import ValidationError
from django.db import models

def get_client(request):
    idClient = request.META.get('HTTP_IDCLIENT', None)
    if idClient is None:
        return HttpResponse("Missing idClient header", status=400)
    try:
        idClient = int(idClient)
    except ValueError:
        return HttpResponse("Invalid idClient header", status=400)
    try:
        client = models.Client.objects.get(id=idClient)
    except models.Client.DoesNotExist:
        return HttpResponse("Client not found", status=404)
    return HttpResponse(client)
        
        

The updated code uses Django's built-in ORM (Object-Relational Mapping) to interact with the database. This provides a layer of abstraction between the application and the database, and automatically escapes any SQL special characters, preventing SQL injection.

The 'idClient' header is first retrieved from the request. If it is not present, a 400 (Bad Request) response is returned. The value of 'idClient' is then converted to an integer. If this fails (for example, if 'idClient' is not a valid integer), a 400 response is returned.

The models.Client.objects.get(id=idClient) line attempts to retrieve a client with the given ID from the database. If no such client exists, a 404 (Not Found) response is returned. If a client is found, it is returned in the response.

This code assumes that there is a Client model defined in the models module. If this is not the case, you will need to replace models.Client with the appropriate model class.

This code also assumes that the database user used by the application has only the minimum necessary permissions. This is a good security practice, as it limits the potential damage if an attacker manages to inject SQL.

References