Skip to main content

Overview

SQL Injection is a code injection technique that allows an attacker to interfere with the queries that an application makes to its database. This is typically caused by the application incorporating unsanitized user input directly into an SQL statement.

Business Impact

A successful SQLi attack can result in the complete compromise of the database, leading to unauthorized access to all user data, intellectual property, and credentials. It can also be used to modify or delete data, and in some cases, achieve remote code execution on the server, making it one of the most critical web application vulnerabilities.

Reference Details

CWE ID: CWE-89 OWASP Top 10 (2021): A03:2021 - Injection Severity: Critical

Framework-Specific Analysis and Remediation

The primary defense against SQL Injection is to strictly separate code from data by using parameterized queries or Object-Relational Mapping (ORM) features that build them automatically.
  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Django’s built-in ORM is the primary defense against SQLi. Its querysets automatically parameterize inputs, effectively eliminating the risk for most database operations. Vulnerabilities typically arise when developers bypass the ORM and write raw SQL queries without proper parameterization.

Vulnerable Scenario 1: Raw SQL in a Search Endpoint

A product search feature uses Manager.raw() to accommodate a complex query, but formats the user’s search term directly into the query string.
# products/views.py
from django.db import connection
from rest_framework.views import APIView
from rest_framework.response import Response

class ProductSearchView(APIView):
    def get(self, request):
        query = request.query_params.get('q', '')
        # DANGEROUS: User input is formatted directly into the raw SQL query.
        # An attacker could provide: "' OR 1=1; --" to list all products.
        sql_query = f"SELECT * FROM products_product WHERE name LIKE '%{query}%' AND is_active = true"
        
        # While Manager.raw() can be safe with params, direct execution is not.
        with connection.cursor() as cursor:
            cursor.execute(sql_query)
            # ... process results ...
        return Response({"message": "Search results for: " + query})

Vulnerable Scenario 2: Dynamic Sorting in a Reporting API

An internal reporting API allows sorting by a column name provided in the URL, which is then concatenated into the ORDER BY clause.
# reports/views.py
def generate_sales_report(request):
    sort_column = request.GET.get('sort', 'sale_date')
    # DANGEROUS: The column name is not validated. An attacker could inject
    # a complex SQL statement here, for example:
    # "(CASE WHEN (SELECT password FROM auth_user WHERE username = 'admin') = 'abc' THEN id ELSE name END)"
    # This allows for blind, time-based SQL injection to extract data.
    query = f"SELECT * FROM sales_report ORDER BY {sort_column}"
    # ... execute raw query ...
    return HttpResponse("Report generated.")

Mitigation and Best Practices

Always prefer the Django ORM over raw SQL. If raw SQL is necessary, use the params argument to let the database driver handle parameterization. For dynamic elements like column names, always validate the user input against a strict allow-list of valid values.

Secure Code Example

# products/views.py (Secure Version)
from .models import Product
from .serializers import ProductSerializer

class ProductSearchView(APIView):
    def get(self, request):
        query = request.query_params.get('q', '')
        
        # SAFE: The Django ORM handles sanitization and parameterization automatically.
        products = Product.objects.filter(name__icontains=query, is_active=true)
        
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

Testing Strategy

Write integration tests using Django’s APITestCase that attempt to inject malicious SQL payloads into the search parameter. Assert that the API returns an expected (non-vulnerable) response and does not leak unintended data.
# products/tests.py
from rest_framework.test import APITestCase

class ProductSearchTests(APITestCase):
    def test_search_with_sql_injection_payload(self):
        # Create some products...
        injection_payload = "' OR 1=1; --"
        response = self.client.get(f'/api/products/search?q={injection_payload}')
        
        # A vulnerable endpoint might return all products.
        # A secure endpoint should return an empty list for this literal query.
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), 0)