FlawOpen

flawopen.com/SQL Injection in Python/Django ORM

Is Django's ORM safe from SQL injection by default?

Reference page
Short answer

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

UNSAFE — interpolate input in raw SQL
username = request.GET.get("u", "")
User.objects.raw(
    f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
SAFE — bind input with raw() params
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

Model.objects.raw()

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.

connection.cursor() direct access

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.

Field lookups built from user-controlled strings

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' .
Review every match and trace values into the query. Check for bound params, untrusted SQL fragments, dynamically selected identifiers or lookup paths, and custom Func templates or extra_context.

FAQ

Is extra() safe?

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.

Does this apply the same way to Django REST Framework?

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

See also SQL Injection (CWE-89): How It Works and How to Prevent It in Every Language — flawopen.com Doctrine ORM SQL Injection: Safe Queries and Pitfalls EF Core FromSqlRaw: Safe Parameterized SQL FastAPI SQL Injection: What Pydantic Does and Doesn’t Stop GORM Raw SQL: Prevent SQL Injection in Go Is Escaping Quotes Enough to Prevent SQL Injection? Is Using an ORM Enough to Prevent SQL Injection?