Sql Injection in Django with Bearer Tokens
Sql Injection in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
SQL injection remains a critical risk in Django when dynamic query construction is used, even when APIs are protected by Bearer token authentication. Bearer tokens handle identity verification and authorization, but they do not influence how queries are built. If a developer builds SQL using string formatting or concatenation—such as cursor.execute("SELECT * FROM api_user WHERE id = %s AND token = '" + token + "'")—the token value is treated as data, not as a mechanism to safely parameterize the statement. Attackers can supply a malicious Bearer token containing SQL fragments, for example Bearer ' OR 1=1; --, and if the application naively interpolates that value into raw SQL, the attacker can bypass intended access controls and extract or modify data.
Django’s ORM protects against classic SQL injection when you use its query API correctly. However, the combination of Bearer tokens and raw SQL often arises in scenarios such as token introspection, per-request filtering, or logging, where developers write raw queries for performance or operational reasons. In these cases, the token is just another variable. If that variable is inserted into a query string without parameterization, the database treats it as executable SQL. The presence of a Bearer token does not reduce the impact; it may actually increase the impact by enabling authenticated paths that expose more sensitive data. Common patterns include using token values in WHERE clauses to implement row-level filtering or using token metadata (e.g., scopes or roles) in dynamic ORDER BY or GROUP BY clauses.
The risk is compounded when APIs accept untrusted input in query parameters or headers and combine it with raw SQL. For example, a developer might write cursor.execute("SELECT * FROM logs WHERE user_id = %s AND auth_token = %s", [user_id, auth_token]) but fail to validate that auth_token is a simple string. If the underlying database adapter or Django version introduces subtle behavior changes, or if the developer mistakenly uses string formatting instead of parameters, the query can become vulnerable. Because Bearer tokens are often long-lived and reused across requests, a single vulnerable endpoint can expose a wide attack surface. This is why it is essential to treat Bearer tokens as untrusted input and apply strict parameterization regardless of their source.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
To mitigate SQL injection when using Bearer tokens in Django, always use parameterized queries and avoid string interpolation. Prefer Django’s ORM wherever possible, and if raw SQL is required, use cursor parameterization with placeholders. Below are concrete, safe patterns.
Safe ORM Usage
Use Django models and querysets to let the framework handle parameterization automatically:
from django.contrib.auth import get_user_model
User = get_user_model()
# Safe: queryset filtering uses parameterization under the hood
user = User.objects.filter(id=user_id, profile__token=auth_token).first()
Safe Raw SQL with Parameterization
When raw SQL is necessary, use cursor.execute with a parameter tuple or dictionary. Never use % formatting or .format() on SQL strings that include token or user-controlled values:
from django.db import connection
def get_logs_for_token(user_id: int, auth_token: str):
with connection.cursor() as cursor:
# Safe: parameters are passed separately and never interpolated into the query string
cursor.execute(
"SELECT * FROM api_log WHERE user_id = %s AND auth_token = %s",
[user_id, auth_token]
)
return cursor.fetchall()
Named Parameter Style (Optional, for clarity)
For complex queries, named parameters can improve readability and reduce mistakes:
def get_user_by_token_named(user_id: int, auth_token: str):
with connection.cursor() as cursor:
cursor.execute(
"SELECT * FROM api_user WHERE id = %(uid)s AND auth_token = %(token)s",
{"uid": user_id, "token": auth_token}
)
return cursor.fetchone()
Validation and Scope Limitation
Even with safe parameterization, validate and constrain the token’s usage. Do not rely on the token to encode business logic that belongs in the query structure. If you only need to verify identity, resolve the user once and use the resolved user ID in queries rather than repeating token checks in SQL:
def safe_view(request):
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
if not auth_header.startswith("Bearer "):
return HttpResponseForbidden()
token = auth_header[len("Bearer "):].strip()
# Validate token via your auth backend, then use user ID
user = authenticate_token(token)
if user is None:
return HttpResponseForbidden()
# Use user.id in parameterized queries instead of re-checking the token in SQL
entries = Entry.objects.filter(owner_id=user.id)
return JsonResponse(list(entries.values("id", "title")), safe=False)
Avoid These Anti-Patterns
- Do not concatenate or interpolate the token into SQL strings:
f"SELECT ... WHERE token = '{auth_token}'". - Do not use
cursor.execute(query % token)orcursor.execute(\"...\".format(token)). - Do not treat the Bearer token as a trusted source for column names, table names, or ordering fields; these must be whitelisted if dynamic.
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 |