codepathfinder 1.2.0__py3-none-manylinux_2_17_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,355 @@
1
+ """
2
+ PYTHON-DJANGO-001: Django SQL Injection in cursor.execute()
3
+
4
+ Security Impact: CRITICAL
5
+ CWE: CWE-89 (SQL Injection)
6
+ CVE: CVE-2022-34265 (Django SQL injection in Trunc/Extract)
7
+ OWASP: A03:2021 - Injection
8
+
9
+ DESCRIPTION:
10
+ This rule detects SQL injection vulnerabilities in Django applications where untrusted user input
11
+ flows directly into raw SQL execution via cursor.execute() or similar methods without proper
12
+ parameterization. This is one of the most critical web security vulnerabilities.
13
+
14
+ WHAT IS SQL INJECTION:
15
+
16
+ SQL injection occurs when an attacker can manipulate SQL queries by injecting malicious input.
17
+ In Django, this typically happens when developers bypass the ORM's built-in protections and
18
+ use raw SQL queries with string formatting or concatenation.
19
+
20
+ SECURITY IMPLICATIONS:
21
+
22
+ **1. Data Breach**:
23
+ Attackers can extract sensitive data from your database, including:
24
+ - User credentials (passwords, API keys)
25
+ - Personal information (emails, addresses, SSNs)
26
+ - Business data (financial records, trade secrets)
27
+
28
+ **2. Data Manipulation**:
29
+ Attackers can modify or delete data:
30
+ - Update admin privileges
31
+ - Delete critical records
32
+ - Modify financial transactions
33
+
34
+ **3. Authentication Bypass**:
35
+ Bypass login mechanisms entirely:
36
+ ```python
37
+ # User input: ' OR '1'='1
38
+ username = request.GET.get('username') # ' OR '1'='1
39
+ query = f"SELECT * FROM users WHERE username = '{username}'"
40
+ # Resulting query: SELECT * FROM users WHERE username = '' OR '1'='1'
41
+ # This returns ALL users, bypassing authentication
42
+ ```
43
+
44
+ **4. Remote Code Execution** (in some databases):
45
+ - Execute system commands via xp_cmdshell (SQL Server)
46
+ - Read/write files via LOAD_FILE() (MySQL)
47
+ - Access OS through large objects (PostgreSQL)
48
+
49
+ VULNERABLE EXAMPLE:
50
+ ```python
51
+ from django.db import connection
52
+ from django.http import HttpRequest, JsonResponse
53
+
54
+ def get_user_profile(request: HttpRequest):
55
+ \"\"\"
56
+ VULNERABLE: User input flows directly into SQL query.
57
+ An attacker can inject: 1' OR '1'='1
58
+ \"\"\"
59
+ user_id = request.GET.get('user_id') # Source: untrusted input
60
+
61
+ cursor = connection.cursor()
62
+ # DANGEROUS: f-string interpolation in SQL
63
+ query = f"SELECT * FROM users WHERE id = {user_id}"
64
+ cursor.execute(query) # Sink: SQL execution
65
+
66
+ result = cursor.fetchone()
67
+ return JsonResponse({'user': result})
68
+
69
+ # Attack example:
70
+ # GET /profile?user_id=1' OR '1'='1 --
71
+ # Resulting query: SELECT * FROM users WHERE id = 1' OR '1'='1 --
72
+ # Returns all users instead of just one
73
+ ```
74
+
75
+ SECURE EXAMPLE:
76
+ ```python
77
+ from django.db import connection
78
+ from django.http import HttpRequest, JsonResponse
79
+
80
+ def get_user_profile(request: HttpRequest):
81
+ \"\"\"
82
+ SECURE: Uses parameterized queries with placeholders.
83
+ \"\"\"
84
+ user_id = request.GET.get('user_id') # User input
85
+
86
+ cursor = connection.cursor()
87
+ # SAFE: Parameterized query with %s placeholder
88
+ query = "SELECT * FROM users WHERE id = %s"
89
+ cursor.execute(query, [user_id]) # Parameters passed separately
90
+
91
+ result = cursor.fetchone()
92
+ return JsonResponse({'user': result})
93
+
94
+ # Even with attack input, parameters are properly escaped:
95
+ # GET /profile?user_id=1' OR '1'='1
96
+ # Django escapes the input, query becomes:
97
+ # SELECT * FROM users WHERE id = '1\' OR \'1\'=\'1'
98
+ # No SQL injection possible
99
+ ```
100
+
101
+ ALTERNATIVE SECURE APPROACHES:
102
+
103
+ **1. Use Django ORM** (Recommended):
104
+ ```python
105
+ from django.contrib.auth.models import User
106
+
107
+ def get_user_profile(request):
108
+ user_id = request.GET.get('user_id')
109
+ # Django ORM automatically parameterizes queries
110
+ user = User.objects.filter(id=user_id).first()
111
+ return JsonResponse({'user': {'id': user.id, 'username': user.username}})
112
+ ```
113
+
114
+ **2. Use Django's escape_sql()** (for complex cases):
115
+ ```python
116
+ from django.db import connection
117
+ from django.db.backends.utils import escape_sql
118
+
119
+ def complex_query(request):
120
+ search = request.GET.get('q')
121
+ escaped = escape_sql(search)
122
+ # Still use parameterized queries for values
123
+ query = "SELECT * FROM products WHERE name LIKE %s"
124
+ cursor.execute(query, [f'%{escaped}%'])
125
+ ```
126
+
127
+ **3. Avoid .raw() and .extra()** (deprecated):
128
+ ```python
129
+ # AVOID THIS (vulnerable if not careful):
130
+ User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
131
+
132
+ # USE THIS INSTEAD:
133
+ User.objects.raw("SELECT * FROM users WHERE name = %s", [name])
134
+ ```
135
+
136
+ DETECTION AND PREVENTION:
137
+
138
+ **Pre-deployment checks**:
139
+ ```bash
140
+ # Scan Django project for SQL injection
141
+ pathfinder scan --project . --ruleset cpf/python/PYTHON-DJANGO-001
142
+
143
+ # Run static analysis in CI/CD
144
+ # .github/workflows/security.yml:
145
+ # - name: Scan for SQL injection
146
+ # run: pathfinder ci --project . --ruleset cpf/python/django
147
+ ```
148
+
149
+ **Code Review Checklist**:
150
+ - [ ] All cursor.execute() calls use parameterized queries (%s placeholders)
151
+ - [ ] No f-strings or .format() in SQL queries
152
+ - [ ] No string concatenation (+) in SQL queries
153
+ - [ ] ORM .raw() and .extra() methods use parameterization
154
+ - [ ] User input never directly interpolated into SQL
155
+
156
+ **Django Settings** (enable strict mode):
157
+ ```python
158
+ # settings.py
159
+ DEBUG = False # Never True in production
160
+
161
+ DATABASES = {
162
+ 'default': {
163
+ # ... other settings
164
+ 'OPTIONS': {
165
+ 'sql_mode': 'STRICT_ALL_TABLES', # MySQL/MariaDB
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ REAL-WORLD ATTACK SCENARIOS:
172
+
173
+ **1. Union-Based Injection**:
174
+ ```python
175
+ # Attack: ?user_id=1 UNION SELECT username,password FROM admin_users--
176
+ # Attacker retrieves admin credentials
177
+ ```
178
+
179
+ **2. Boolean-Based Blind Injection**:
180
+ ```python
181
+ # Attack: ?user_id=1 AND 1=1 (returns results)
182
+ # Attack: ?user_id=1 AND 1=2 (returns nothing)
183
+ # Attacker infers database structure bit by bit
184
+ ```
185
+
186
+ **3. Time-Based Blind Injection**:
187
+ ```python
188
+ # Attack: ?user_id=1 AND SLEEP(5)
189
+ # If response is slow, injection successful
190
+ # Attacker extracts data one bit at a time
191
+ ```
192
+
193
+ COMPLIANCE AND AUDITING:
194
+
195
+ **CIS Django Benchmark**:
196
+ > "All database queries must use parameterized statements"
197
+
198
+ **OWASP Top 10**:
199
+ SQL Injection is consistently ranked in Top 3 most critical web vulnerabilities
200
+
201
+ **PCI DSS Requirement 6.5.1**:
202
+ > "Injection flaws, particularly SQL injection"
203
+
204
+ **SOC 2 / ISO 27001**:
205
+ Requires input validation and parameterized queries
206
+
207
+ **GDPR Article 32**:
208
+ SQL injection can lead to massive data breaches subject to fines
209
+
210
+ MIGRATION GUIDE:
211
+
212
+ **Step 1: Identify all raw SQL usage**:
213
+ ```bash
214
+ # Find all cursor.execute calls
215
+ grep -r "cursor.execute" --include="*.py"
216
+
217
+ # Find all .raw() usage
218
+ grep -r ".raw(" --include="*.py"
219
+ ```
220
+
221
+ **Step 2: Replace with parameterized queries**:
222
+ ```python
223
+ # BEFORE
224
+ query = f"SELECT * FROM users WHERE email = '{email}'"
225
+ cursor.execute(query)
226
+
227
+ # AFTER
228
+ query = "SELECT * FROM users WHERE email = %s"
229
+ cursor.execute(query, [email])
230
+ ```
231
+
232
+ **Step 3: Test thoroughly**:
233
+ ```python
234
+ # Test with SQL injection payloads
235
+ test_payloads = [
236
+ "1' OR '1'='1",
237
+ "1'; DROP TABLE users--",
238
+ "1 UNION SELECT password FROM admin",
239
+ ]
240
+
241
+ for payload in test_payloads:
242
+ response = client.get(f'/api/user?id={payload}')
243
+ # Should NOT return unauthorized data or cause errors
244
+ ```
245
+
246
+ **Step 4: Add automated testing**:
247
+ ```python
248
+ # tests/test_security.py
249
+ from django.test import TestCase
250
+
251
+ class SQLInjectionTests(TestCase):
252
+ def test_user_profile_sql_injection(self):
253
+ # Attempt SQL injection
254
+ response = self.client.get("/profile?id=1' OR '1'='1")
255
+ # Should not expose all users
256
+ self.assertNotContains(response, "admin@")
257
+ ```
258
+
259
+ FRAMEWORK-SPECIFIC NOTES:
260
+
261
+ **Django 4.2+**:
262
+ - QuerySet.extra() is deprecated (use .annotate() instead)
263
+ - QuerySet.raw() still supported but requires careful use
264
+
265
+ **Django REST Framework**:
266
+ ```python
267
+ # Vulnerable
268
+ def get_queryset(self):
269
+ user_id = self.request.query_params.get('id')
270
+ return User.objects.raw(f"SELECT * FROM users WHERE id = {user_id}")
271
+
272
+ # Secure
273
+ def get_queryset(self):
274
+ user_id = self.request.query_params.get('id')
275
+ return User.objects.filter(id=user_id) # Use ORM
276
+ ```
277
+
278
+ REFERENCES:
279
+ - CWE-89: SQL Injection (https://cwe.mitre.org/data/definitions/89.html)
280
+ - CVE-2022-34265: Django SQL injection in Trunc/Extract
281
+ - OWASP A03:2021 - Injection (https://owasp.org/Top10/A03_2021-Injection/)
282
+ - Django Security Docs: https://docs.djangoproject.com/en/stable/topics/security/
283
+ - OWASP SQL Injection Prevention Cheat Sheet
284
+ - Bobby Tables: https://bobby-tables.com/python
285
+
286
+ DETECTION SCOPE:
287
+ This rule performs intra-procedural analysis only. It detects SQL injection when both the
288
+ source (user input) and sink (SQL execution) are in the same function. It will NOT detect
289
+ cases where user input is passed through multiple function calls before reaching SQL execution.
290
+
291
+ LIMITATION:
292
+ - Only detects flows within a single function (intra-procedural)
293
+ - Does not track dataflow across function boundaries (inter-procedural)
294
+ - May miss complex multi-function SQL injection patterns
295
+ """
296
+
297
+ from rules.python_decorators import python_rule
298
+ from codepathfinder import calls, flows
299
+ from codepathfinder.presets import PropagationPresets
300
+
301
+
302
+ @python_rule(
303
+ id="PYTHON-DJANGO-001",
304
+ name="Django SQL Injection in cursor.execute()",
305
+ severity="CRITICAL",
306
+ category="django",
307
+ cwe="CWE-89",
308
+ cve="CVE-2022-34265",
309
+ tags="python,django,sql-injection,orm,database,owasp-a03,cwe-89,parameterization,cursor,intra-procedural,critical,security",
310
+ message="SQL injection vulnerability: User input flows to cursor.execute() without parameterization within a function. Use parameterized queries with %s placeholders.",
311
+ owasp="A03:2021",
312
+ )
313
+ def detect_django_sql_injection():
314
+ """
315
+ Detects SQL injection where user input flows to Django cursor.execute() within a single function.
316
+
317
+ LIMITATION: Only detects intra-procedural flows (within one function).
318
+ Will NOT detect if request.GET is in one function and cursor.execute is in another.
319
+
320
+ Example vulnerable code:
321
+ user_id = request.GET.get('id')
322
+ query = f"SELECT * FROM users WHERE id = {user_id}"
323
+ cursor.execute(query)
324
+ """
325
+ return flows(
326
+ from_sources=[
327
+ calls("request.GET.get"),
328
+ calls("request.POST.get"),
329
+ calls("request.GET"),
330
+ calls("request.POST"),
331
+ calls("request.COOKIES"),
332
+ calls("request.FILES"),
333
+ calls("*.GET.get"),
334
+ calls("*.POST.get"),
335
+ calls("*.GET"),
336
+ calls("*.POST"),
337
+ ],
338
+ to_sinks=[
339
+ calls("execute"),
340
+ calls("cursor.execute"),
341
+ calls("*.execute"),
342
+ calls("*.raw"),
343
+ calls("*.extra"),
344
+ ],
345
+ sanitized_by=[
346
+ calls("escape"),
347
+ calls("escape_string"),
348
+ calls("escape_sql"),
349
+ calls("*.escape"),
350
+ calls("*.escape_string"),
351
+ calls("*.escape_sql"),
352
+ ],
353
+ propagates_through=PropagationPresets.standard(),
354
+ scope="local", # CRITICAL: Only intra-procedural analysis works
355
+ )
File without changes