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.
- codepathfinder/__init__.py +48 -0
- codepathfinder/bin/pathfinder +0 -0
- codepathfinder/cli/__init__.py +204 -0
- codepathfinder/config.py +92 -0
- codepathfinder/dataflow.py +193 -0
- codepathfinder/decorators.py +158 -0
- codepathfinder/ir.py +107 -0
- codepathfinder/logic.py +101 -0
- codepathfinder/matchers.py +243 -0
- codepathfinder/presets.py +135 -0
- codepathfinder/propagation.py +250 -0
- codepathfinder-1.2.0.dist-info/METADATA +111 -0
- codepathfinder-1.2.0.dist-info/RECORD +33 -0
- codepathfinder-1.2.0.dist-info/WHEEL +5 -0
- codepathfinder-1.2.0.dist-info/entry_points.txt +2 -0
- codepathfinder-1.2.0.dist-info/licenses/LICENSE +661 -0
- codepathfinder-1.2.0.dist-info/top_level.txt +2 -0
- rules/__init__.py +36 -0
- rules/container_combinators.py +209 -0
- rules/container_decorators.py +223 -0
- rules/container_ir.py +104 -0
- rules/container_matchers.py +230 -0
- rules/container_programmatic.py +115 -0
- rules/python/__init__.py +0 -0
- rules/python/deserialization/__init__.py +0 -0
- rules/python/deserialization/pickle_loads.py +479 -0
- rules/python/django/__init__.py +0 -0
- rules/python/django/sql_injection.py +355 -0
- rules/python/flask/__init__.py +0 -0
- rules/python/flask/debug_mode.py +374 -0
- rules/python/injection/__init__.py +0 -0
- rules/python_decorators.py +177 -0
- rules/python_ir.py +80 -0
|
@@ -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
|