Sql Injection in Django with Mutual Tls
Sql Injection in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
SQL injection in Django occurs when untrusted input is concatenated into database queries, allowing an attacker to alter query logic. Django’s ORM largely protects against classic SQLi when querysets are used as intended, but raw SQL execution via cursor.execute() or Manager.raw() without parameterization remains risky. Mutual TLS (mTLS) secures transport layer authentication—client certificates are validated before application logic runs—but it does not change how queries are built. The combination can expose SQL injection because mTLS may give an attacker implicit trust or access to endpoints that use raw queries, encouraging a false sense of security.
Consider a Django view that authenticates a request with mTLS (client certificate validated) and then builds SQL dynamically:
from django.db import connection
def search_users(request):
name = request.GET.get('name', '')
with connection.cursor() as cursor:
cursor.execute(f'SELECT id, email FROM auth_user WHERE name = \'{name}\'')
rows = cursor.fetchall()
return JsonResponse(list(rows), safe=False)
Even with mTLS ensuring the client is who they claim to be, the name parameter is interpolated directly into the SQL string, enabling injection (e.g., name=' OR 1=1 --). mTLS does not mitigate injection; it only authenticates the connection. Additionally, mTLS endpoints often expose administrative or sensitive operations that developers might treat as trusted, leading to skipped input validation. OWASP API Top 10 A03:2023 (Injection) remains relevant, and frameworks like Django require disciplined use of parameterized queries regardless of transport protections.
Key risk pattern: using mTLS to gate raw SQL routes without enforcing strict input validation and query parameterization. Attackers who obtain or spoof a valid client certificate can directly exploit SQL injection to extract, modify, or delete data. This highlights why transport-layer controls must be complemented with secure coding practices.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation centers on avoiding raw SQL interpolation and using parameterized queries even when mTLS is in place. Below are concrete, safe patterns for Django with mTLS examples.
1. Safe parameterized query with mTLS client validation
Use connection.cursor() with parameterized queries. Django’s cursor.execute(sql, params) supports passing parameters separately, which ensures proper escaping.
from django.db import connection
def search_users(request):
name = request.GET.get('name', '')
# mTLS validation would occur earlier (e.g., via request attributes or middleware)
with connection.cursor() as cursor:
cursor.execute('SELECT id, email FROM auth_user WHERE name = %s', [name])
rows = cursor.fetchall()
return JsonResponse([dict(row) for row in rows], safe=False)
2. Using Django’s ORM to avoid raw SQL entirely
Prefer the ORM, which automatically parameterizes queries. This approach is robust and eliminates injection risk for standard lookups.
from django.contrib.auth.models import User
def search_users_orm(request):
name = request.GET.get('name', '')
users = User.objects.filter(name=name)
return JsonResponse(list(users.values('id', 'email')), safe=False)
3. Enforcing mTLS and safe query practices together (middleware example)
You can implement a simple middleware to validate client certificates and ensure safe handling downstream. This example assumes a certificate attribute is set on the request after mTLS verification.
import ssl
from django.http import JsonResponse
class MutualTlsValidationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# In production, use proper cert validation via your web server (e.g., Nginx/Apache)
# and set request attributes after successful verification.
if not hasattr(request, 'client_cert_valid') or not request.client_cert_valid:
return JsonResponse({'error': 'Client certificate required'}, status=403)
return self.get_response(request)
4. Example with explicit query parameterization and logging
import logging
logger = logging.getLogger(__name__)
def get_user_by_id(request, user_id):
# Assume user_id is validated as integer
try:
uid = int(user_id)
except ValueError:
return JsonResponse({'error': 'Invalid user ID'}, status=400)
with connection.cursor() as cursor:
cursor.execute('SELECT id, email FROM auth_user WHERE id = %s', [uid])
row = cursor.fetchone()
if row:
return JsonResponse(dict(zip([col[0] for col in cursor.description], row)))
return JsonResponse({'error': 'Not found'}, status=404)
These examples emphasize parameterization and ORM usage regardless of mTLS. mTLS secures who can connect, but SQL safety must be enforced in code. Findings from scans will flag raw SQL interpolation as high severity, with remediation guidance to use parameterized queries or ORM filters.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |