Interactive Playground
Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.
pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-002 --project .About This Rule
Understanding the vulnerability and how it is detected
This rule detects SQL injection vulnerabilities in Django applications where untrusted user input from HTTP request parameters flows into the SQL string argument of Model.objects.raw() without proper parameterization.
Django's Model.objects.raw() provides a way to execute raw SQL queries while returning model instances. It accepts an optional params argument for safe parameterization, similar to cursor.execute(). When developers bypass this by embedding request parameters directly into the SQL string through f-strings or concatenation, attackers can inject arbitrary SQL. Since raw() operates at the ORM level, developers often have a false sense of security, not realizing that the SQL string is passed directly to the database driver without any escaping.
The rule uses inter-procedural taint analysis to follow data from Django request accessors through helper functions to the raw() call across file and module boundaries.
Security Implications
Potential attack scenarios if this vulnerability is exploited
ORM-Level SQL Injection
Developers using raw() sometimes believe the ORM provides automatic protection. It does not for the SQL string argument. An attacker can inject UNION SELECT clauses to return rows from arbitrary tables, bypassing any model-level access control the application enforces.
Mass Data Exfiltration
Because raw() returns model instances, injected UNION queries that match the expected column structure can make the application silently serve attacker-chosen data. This is particularly dangerous in APIs where the full response is serialized and returned to the caller.
Authentication and Authorization Bypass
If raw() is used in authentication-related queries, injection can bypass login checks or privilege verification, granting attackers access to admin functionality or other users' accounts without valid credentials.
Schema Discovery via Information Schema
Attackers can query information_schema tables through injection to enumerate all tables, columns, and relationships in the database, providing a blueprint for further targeted attacks against sensitive data.
How to Fix
Recommended remediation steps
- 1Always pass user-controlled values via the params argument of raw() as a list or dict, never by embedding them in the SQL string itself.
- 2Prefer standard Django ORM queryset methods such as filter(), exclude(), and annotate() over raw() to eliminate this vulnerability class entirely.
- 3When raw() is required for complex queries, keep the SQL template as a static string literal and never construct it dynamically from user input.
- 4Validate the type and format of parameters before passing them to raw(), particularly enforcing integer types for ID fields using int() conversion.
- 5Review all occurrences of .raw() in the codebase during security reviews to ensure every call uses the params argument.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
This rule performs inter-procedural taint analysis with global scope. Sources include calls("request.GET.get"), calls("request.POST.get"), calls("request.GET.__getitem__"), calls("request.POST.__getitem__"), calls("request.body"), and calls("request.read"). The sink is the first argument (SQL string, tracked via .tracks(0)) of calls("*.raw"). The second argument (params list) is explicitly not tracked, avoiding false positives on correctly parameterized raw() calls. Sanitizers include int(), float(), and explicit allowlist validation functions. The rule follows taint across file boundaries and through intermediate variable assignments and function call chains.
Compliance & Standards
Industry frameworks and regulations that require detection of this vulnerability
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Django SQL Injection via cursor.execute()
User input flows to cursor.execute() without parameterization, enabling SQL injection attacks.
Django SQL Injection via QuerySet.extra()
User input flows to QuerySet.extra() without parameterization, enabling SQL injection through Django's legacy ORM extension interface.
Django SQL Injection via RawSQL Expression
User input flows to RawSQL() expression without parameterization, enabling SQL injection through Django's annotation system.
Frequently Asked Questions
Common questions about Django SQL Injection via QuerySet.raw()
New feature
Get these findings posted directly on your GitHub pull requests
The Django SQL Injection via QuerySet.raw() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.