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-001 --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 raw SQL queries passed to cursor.execute() without proper parameterization.
Django's ORM provides safe, parameterized query construction by default, but developers sometimes bypass this protection by dropping down to raw SQL using Django's database connection cursor. When request.GET, request.POST, request.body, or other user-controlled data is concatenated or f-stringed into a SQL string before being passed to cursor.execute(), an attacker can inject arbitrary SQL, read or modify all database records, bypass authentication, or escalate to OS-level commands on vulnerable database servers.
The rule uses inter-procedural taint analysis to follow data across function calls and file boundaries, catching patterns like query strings built in one function and executed in another.
Security Implications
Potential attack scenarios if this vulnerability is exploited
Full Database Compromise
An attacker who controls the SQL string passed to cursor.execute() can use UNION SELECT to read from any table in the database, including user credentials, session tokens, and private records. There is no row-level restriction once the query is fully attacker-controlled.
Authentication Bypass
Login queries built with string concatenation can be bypassed with the classic ' OR '1'='1 payload. The query returns all rows, the application logs in as the first user found, which is typically an admin account.
Data Manipulation and Destruction
Stacked queries or subquery injection can issue INSERT, UPDATE, or DELETE statements through the same cursor.execute() call, allowing attackers to alter financial records, create backdoor accounts, or destroy application data.
Database Server Escalation
On PostgreSQL, COPY TO/FROM can read or write server filesystem files. On MySQL, INTO OUTFILE and LOAD DATA INFILE enable similar file access. These paths can escalate from SQL injection to full server compromise.
How to Fix
Recommended remediation steps
- 1Always pass user input as the second argument to cursor.execute() as a list or tuple, never by concatenating it into the SQL string.
- 2Prefer Django ORM methods (filter(), exclude(), annotate()) over raw SQL to benefit from automatic parameterization.
- 3Validate and restrict the format of input used in SQL queries (e.g., enforce integer type for ID parameters) before it reaches the database layer.
- 4Grant database accounts the minimum required privileges so that a compromised query cannot DROP tables or access unrelated schemas.
- 5Enable Django's database query logging in development to audit all SQL statements and catch unparameterized queries early.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
This rule performs inter-procedural taint analysis with global scope. Sources are Django HTTP request accessor calls: 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 (position 0, tracked via .tracks(0)) of calls("*.execute") matching Django database cursor objects. Flows that pass through django.utils.html.escape() or explicit type conversion to int/float are treated as sanitized. The rule follows tainted values through variable assignments, function arguments, and return values across file boundaries.
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 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.
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 cursor.execute()
New feature
Get these findings posted directly on your GitHub pull requests
The Django SQL Injection via cursor.execute() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.