flawopen.com/SQL Injection in Python/Django ORM
Is Django's ORM safe from SQL injection by default?
Django’s QuerySet API keeps SQL code separate from values, so ordinary lookups such as .filter(), .get(), and .exclude() pass values as database parameters. That protection does not cover every way to build a query: raw SQL, custom expressions with interpolated context, and user-selected query structure need separate review.
Safe value handling in Django QuerySets
username = request.GET.get("u", "")
User.objects.raw(
f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
username = request.GET.get("u", "")
User.objects.raw(
"SELECT * FROM myapp_user WHERE username = %s",
[username],
)
Raw SQL and dynamic query structure need explicit rules
Manager.raw() runs SQL that you provide. Bind user-controlled values through params and leave placeholders unquoted, for example User.objects.raw("SELECT * FROM users WHERE username = %s", [username]). Do not use f-strings, % formatting, concatenation, or quoted placeholders to insert values into SQL. Parameters represent values, not SQL identifiers such as table or column names.
For direct database access, call cursor.execute(sql, params) and pass values separately using Django's documented placeholder style. Do not interpolate values into sql before execution. Review custom RawSQL and Func expressions too: template context and keyword arguments may be interpolated into SQL instead of bound as parameters.
A user-controlled field path passed to .filter(**{field: value}) is not automatically SQL injection; Django resolves lookup names as query structure. It can still expose fields, relations, or lookups the endpoint did not intend to offer, and invalid paths can cause errors. Map external sort/filter choices through a narrow allow-list of known model fields and permitted lookups.
How to audit Django queries
rg -n '(\.raw\(|RawSQL\(|\.extra\(|connection\.cursor\()' --glob '*.py' .FAQ
QuerySet.extra() is a legacy API for adding raw SQL fragments. Avoid it in new code and review existing where and select arguments carefully. Prefer QuerySet expressions; where raw SQL is necessary, bind values with the documented params argument.
Django REST Framework does not change the ORM’s SQL parameterization behavior. The same rules apply to queries created by views, serializers, and viewsets; separately allow-list any client-controlled ordering or filter fields.
References
- Django docs — SQL injection protection
- flawopen.com — SQL Injection in Python
- Django docs — Performing raw SQL queries
- Django docs — Query expressions and RawSQL