Sql Injection in Django with Cockroachdb
Sql Injection in Django with Cockroachdb — how this specific combination creates or exposes the vulnerability
SQL injection occurs when untrusted input is concatenated into SQL strings and executed by the database. With CockroachDB, the PostgreSQL-wire compatible protocol means standard Django database parameterization rules apply, but misunderstandings about how Django’s ORM and raw query handling interact with CockroachDB can still lead to injection.
Django’s primary defense is the query parameterization provided by cursor.execute(sql, params) and the ORM’s filter()/exclude() methods. These ensure values are sent separately from SQL syntax, which CockroachDB respects over the PostgreSQL protocol. However, injection becomes possible when developers bypass these protections. Examples include:
- Using Python string formatting or concatenation with raw SQL, e.g.,
f"SELECT * FROM accounts WHERE id = {user_id}", or"SELECT * FROM accounts WHERE id = %s" % user_id. - Passing unsafe strings to
extra(select=...),raw(), orexecute_sql()without parameterization. - Assuming CockroachDB’s strict typing or escaping routines alone prevent injection, rather than relying on parameterized queries.
Even though CockroachDB implements a PostgreSQL wire protocol and uses a SQL parser aligned with PostgreSQL behavior, it does not perform additional magic to sanitize inputs that are already embedded into the SQL string by the client. If the client sends a string containing injected SQL, CockroachDB will execute it. Therefore, the risk with Django + CockroachDB is not a CockroachDB-specific flaw, but a consequence of writing queries that do not keep data and commands separate.
Common attack patterns include authentication bypass via tautologies (e.g., ' OR 1=1 --) and data exfiltration using union-based or error-based techniques. Because CockroachDB supports standard PostgreSQL syntax, payloads that work against PostgreSQL are also effective when improperly parameterized inputs are passed through Django.
Cockroachdb-Specific Remediation in Django — concrete code fixes
Remediation centers on strict use of parameterized queries and avoiding string interpolation. Below are concrete, CockroachDB-compatible code examples for Django.
Safe Query Patterns
Always use Django’s ORM for filtering and updates. The ORM generates parameterized SQL under the hood, which works correctly with CockroachDB.
# Safe: using filter with keyword arguments
users = Account.objects.filter(email=email, status='active')
# Safe: using Q objects for complex conditions
from django.db.models import Q
users = Account.objects.filter(Q(email=email) | Q(username=username))
# Safe: using get with parameters
try:
account = Account.objects.get(account_id=account_id)
except Account.DoesNotExist:
account = None
When raw SQL is unavoidable, use cursor.execute(sql, params) with a params tuple or dict. Never use string formatting to inject values.
# Safe raw query with parameterization
from django.db import connection
def get_accounts_by_country(country):
with connection.cursor() as cursor:
cursor.execute(
"SELECT id, name FROM accounts WHERE country = %s",
[country]
)
return cursor.fetchall()
For extra(), pass select params separately:
# Safe extra with parameters
accounts = Account.objects.extra(
select={'country_upper': "UPPER(country)"},
where=["country = %s"],
params=[country]
)
Avoid raw() with unsanitized input. If you must use raw(), parameterize strictly:
# Safe raw() with parameters
accounts = Account.objects.raw(
"SELECT * FROM accounts WHERE country = %s AND status = %s",
[country, status]
)
CockroachDB-Specific Considerations
CockroachDB’s PostgreSQL compatibility means standard Django database backends work, but ensure your database engine is set to django.contrib.gis.db.backends.postgis or a compatible backend if using PostGIS extensions. Use CockroachDB connection parameters in settings as you would for PostgreSQL:
# settings.py example for CockroachDB
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'USER': 'myuser',
'PASSWORD': 'mypassword',
'HOST': 'cockroachdb-host.mycompany.com',
'PORT': '26257',
'OPTIONS': {
'sslmode': 'require',
},
}
}
Validate and sanitize inputs before they reach the ORM. Use Django forms and model field validation to constrain data types and lengths. For dynamic column or table names (which cannot be parameterized), use an allowlist approach:
# Safe dynamic column name with allowlist
def dynamic_sort(ordering):
allowed = {'created_at', 'name', 'email'}
if ordering not in allowed:
raise ValueError('Invalid ordering field')
return Account.objects.order_by(ordering)
Finally, test your queries against CockroachDB to confirm the generated SQL is as expected. Use Django’s connection.queries in development (with DEBUG=True) to review that parameters are being passed separately and not interpolated into the SQL string.
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 |