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-004 --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 RawSQL() expressions without proper parameterization.
Django's RawSQL() expression allows embedding raw SQL fragments within ORM querysets using annotate(), filter(), and order_by(). It is designed as a safe replacement for the deprecated extra() method and explicitly supports a params argument for parameterized values. When developers embed request parameters directly into the SQL string of a RawSQL() expression instead of using the params argument, they introduce SQL injection at the ORM annotation level.
RawSQL injection is particularly insidious because the expression appears in a modern, ORM-integrated pattern, leading developers to assume ORM-level protection exists. The SQL string is passed verbatim to the database query builder without escaping.
Security Implications
Potential attack scenarios if this vulnerability is exploited
Annotation-Level Data Exfiltration
An attacker who controls the SQL string of a RawSQL() annotation can use subqueries to exfiltrate data from arbitrary tables, embedding the results as annotation values in the queryset response. The application may serialize these attacker-controlled annotation values back to the client.
WHERE Clause Manipulation via Filtered Annotations
When RawSQL() is used in filter() conditions, injection into the SQL string allows attackers to append OR conditions, bypass access controls, or read records belonging to other users by escaping the intended filter logic.
ORDER BY Injection
RawSQL() used in order_by() with user-controlled SQL strings enables ORDER BY injection attacks. On some databases, injection here can trigger time-based blind SQL injection using CASE WHEN and sleep-like functions, allowing data extraction character by character.
Stacked Query Execution
On database backends that support multiple statements (e.g., PostgreSQL with certain drivers), injection into RawSQL() may enable stacked queries, allowing attackers to execute INSERT, UPDATE, DELETE, or DDL statements alongside the intended query.
How to Fix
Recommended remediation steps
- 1Always pass user-controlled values via the params tuple (second argument) of RawSQL(), never by embedding them in the SQL string.
- 2Consider using Django ORM expressions (F(), Value(), Case(), When(), ExpressionWrapper()) as type-safe alternatives to RawSQL() that require no parameterization.
- 3Validate and cast numeric parameters with int() or float() before using them in queries, even when they will be parameterized.
- 4Treat the SQL string argument of RawSQL() as a static template constant -- if it contains runtime values, those must go in params.
- 5Review all RawSQL() usages during code review to verify the params argument is used for any values that could be user-influenced.
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("RawSQL"). The second argument (params tuple) is not tracked. Sanitizers include int(), float(), and explicit allowlist validation. The analysis follows taint chains across file and module boundaries to catch multi-hop flows.
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.raw()
User input flows to QuerySet.raw() without parameterization, enabling SQL injection through Django's ORM raw query interface.
Django SQL Injection via QuerySet.extra()
User input flows to QuerySet.extra() without parameterization, enabling SQL injection through Django's legacy ORM extension interface.
Frequently Asked Questions
Common questions about Django SQL Injection via RawSQL Expression
New feature
Get these findings posted directly on your GitHub pull requests
The Django SQL Injection via RawSQL Expression rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.