Django Tainted SQL String Construction

HIGH

User input is used to construct a SQL string that flows to a database execution function, enabling SQL injection via string building.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangosql-injectionstring-constructionf-stringformattaint-analysisinter-proceduralCWE-89OWASP-A03
CWE References

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-006 --project .
1
2
3
4
5
6
7
8
9
10
11
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

About This Rule

Understanding the vulnerability and how it is detected

This rule detects SQL injection vulnerabilities in Django applications where user input from HTTP request parameters is used to construct SQL strings via f-strings, string concatenation, or % formatting before being passed to a database execution function.

This rule complements the specific sink rules (SEC-001 through SEC-004) by catching the pattern where the SQL construction and execution are in separate functions or files. A common pattern is a view that builds a query string from request parameters and passes it to a database utility function which calls cursor.execute() or raw(). The taint flows through the string construction step and reaches the sink in a different code location.

The rule specifically tracks the construction of SQL strings using user-controlled data, making it effective at catching patterns like: sql = f"SELECT * FROM orders WHERE user_id = {user_id}" or: sql = "SELECT * FROM users WHERE name = '" + username + "'"

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Multi-Hop Injection via String Building

The most dangerous injection patterns involve SQL strings built incrementally across multiple function calls. User input may be appended to a query string in one function, the partially-built string passed to another, and the final string executed in a third. Taint analysis across these hops catches what per-function review would miss.

2

F-String SQL Construction

Python f-strings are concise and natural to write, making them a common pitfall for SQL construction. A query like f"SELECT * FROM t WHERE id = {user_id}" looks harmless but is a direct injection vector. Any value of user_id that contains SQL metacharacters will alter the query structure.

3

Conditional SQL Building Patterns

A common pattern is building WHERE clause conditions dynamically based on which filters a user requests. Each condition appended with concatenation is a separate injection point. This pattern requires careful use of Django's Q() objects or parameterized query fragments instead.

4

Logging and Debugging SQL as a Secondary Path

Developers often construct SQL strings for logging or debugging purposes without intending to execute them, but these strings sometimes end up being executed in error handling paths or when debug logging is disabled. This rule catches construction regardless of the intended use.

How to Fix

Recommended remediation steps

  • 1Never use f-strings, % formatting, or string concatenation to embed user input into SQL strings; use parameterized query placeholders (%s) with a separate values list.
  • 2Refactor dynamic SQL building patterns to use Django's ORM Q() objects, filter chaining, and annotate() for type-safe dynamic query construction.
  • 3When raw SQL is unavoidable, build the SQL string as a static template with %s placeholders and keep all dynamic values in a separate list passed to execute().
  • 4Perform code review specifically looking for SQL string construction that touches request parameters, even when the execution happens in a different function.
  • 5Use Django Debug Toolbar in development to inspect generated SQL and verify parameterization is working correctly.

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 rule tracks taint through string construction operations (f-strings, concatenation, % formatting) and follows the resulting tainted string to SQL execution sinks: calls("*.execute"), calls("*.raw"), calls("*.extra"), and calls("RawSQL"). The .tracks(0) setting ensures only the SQL string argument (not the params argument) of these sinks is considered. Sanitizers include int(), float(), and shlex.quote() for string values.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-89 ranked #3 in 2023 Most Dangerous Software Weaknesses
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - protect web-facing applications against injection attacks
NIST SP 800-53
SI-10: Information Input Validation
GDPR Article 32
Technical measures to ensure data security against unauthorized access

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django Tainted SQL String Construction

SEC-001 through SEC-004 focus on specific sink functions: cursor.execute(), raw(), extra(), and RawSQL(). SEC-006 focuses on the construction of tainted SQL strings regardless of which sink they eventually reach. It catches patterns where the string building happens in a different file or function from the execution, or where the tainted string flows through multiple intermediate variables before reaching a sink.
Yes. This is exactly the use case SEC-006 is designed for. If a Django view calls request.GET.get('filter'), constructs an f-string SQL query, and passes that string to a database utility function in a separate module, the rule follows the taint chain across the function call and flags the construction-to-sink flow.
Yes. Explicit type conversion with int() or float() is recognized as a sanitizer because a successful int() conversion guarantees the value is numeric and cannot contain SQL metacharacters. If the conversion raises ValueError, the application should handle that as invalid input. After a successful int() call, the taint is cleared and subsequent SQL construction using that value will not be flagged.
Column names cannot be parameterized and int/float casting does not apply. For user-controlled column or table names, use an allowlist: validate that the input matches one of a predetermined set of permitted identifiers before including it in the SQL string. Any other approach is unsafe regardless of how the resulting string is executed.
No. The rule tracks data originating from Django request sources. ORM-generated SQL strings are not tainted from the rule's perspective because they are not constructed from user request data. Only strings that contain user-controlled values as a result of f-string, concatenation, or % formatting operations will be flagged.
Replace conditional SQL string building with Django's Q() object pattern: build a list of Q() objects based on the conditions that apply, then combine them with the & and | operators and pass to filter(). This is safe, readable, and testable. For complex cases involving database-specific functions, use RawSQL() with the params argument for each dynamic value.

New feature

Get these findings posted directly on your GitHub pull requests

The Django Tainted SQL String Construction rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works