stryx-cli 0.1.0__py3-none-any.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.
- stryx/__init__.py +35 -0
- stryx/ai/__init__.py +5 -0
- stryx/ai/attack_planner.py +199 -0
- stryx/ai/payload_generator.py +212 -0
- stryx/ai/prompts.py +85 -0
- stryx/ai/providers.py +249 -0
- stryx/attacks/__init__.py +6 -0
- stryx/attacks/attack_chain.py +111 -0
- stryx/attacks/replay.py +186 -0
- stryx/auth/__init__.py +1 -0
- stryx/auth/session_manager.py +335 -0
- stryx/cli.py +675 -0
- stryx/comparison/__init__.py +1 -0
- stryx/comparison/differ.py +255 -0
- stryx/config/__init__.py +6 -0
- stryx/config/default_config.yaml +12 -0
- stryx/config/loader.py +111 -0
- stryx/config/schema.py +80 -0
- stryx/crawler/__init__.py +5 -0
- stryx/crawler/discovery.py +178 -0
- stryx/crawler/graphql_discovery.py +86 -0
- stryx/crawler/html_crawler.py +294 -0
- stryx/crawler/js_endpoints.py +165 -0
- stryx/crawler/openapi.py +76 -0
- stryx/crawler/sitemap.py +94 -0
- stryx/orchestrator.py +347 -0
- stryx/payloads/cmdi.txt +31 -0
- stryx/payloads/header_injection.txt +15 -0
- stryx/payloads/ldap.txt +19 -0
- stryx/payloads/nosqli.txt +21 -0
- stryx/payloads/open_redirect.txt +23 -0
- stryx/payloads/path_traversal.txt +26 -0
- stryx/payloads/sqli.txt +31 -0
- stryx/payloads/ssrf.txt +30 -0
- stryx/payloads/ssti.txt +21 -0
- stryx/payloads/xss.txt +48 -0
- stryx/payloads/xxe.txt +35 -0
- stryx/plugins/__init__.py +5 -0
- stryx/plugins/base.py +85 -0
- stryx/policy/__init__.py +1 -0
- stryx/policy/engine.py +201 -0
- stryx/py.typed +0 -0
- stryx/reports/__init__.py +5 -0
- stryx/reports/generator.py +122 -0
- stryx/reports/json_report.py +72 -0
- stryx/reports/markdown_report.py +101 -0
- stryx/reports/sarif_report.py +200 -0
- stryx/reports/templates/report.html.j2 +163 -0
- stryx/reports/terminal_report.py +138 -0
- stryx/scanners/__init__.py +17 -0
- stryx/scanners/auth.py +444 -0
- stryx/scanners/authorization.py +220 -0
- stryx/scanners/blind.py +328 -0
- stryx/scanners/cloud_ssrf.py +208 -0
- stryx/scanners/cors.py +158 -0
- stryx/scanners/dependencies.py +296 -0
- stryx/scanners/disclosure.py +339 -0
- stryx/scanners/fuzz.py +391 -0
- stryx/scanners/graphql.py +309 -0
- stryx/scanners/injection.py +511 -0
- stryx/scanners/race.py +257 -0
- stryx/signatures/framework_fingerprints.yaml +122 -0
- stryx/signatures/known_vulns.yaml +210 -0
- stryx/utils/__init__.py +7 -0
- stryx/utils/evidence.py +109 -0
- stryx/utils/http_client.py +159 -0
- stryx/utils/logging.py +51 -0
- stryx/utils/rate_limiter.py +37 -0
- stryx_cli-0.1.0.dist-info/METADATA +236 -0
- stryx_cli-0.1.0.dist-info/RECORD +74 -0
- stryx_cli-0.1.0.dist-info/WHEEL +5 -0
- stryx_cli-0.1.0.dist-info/entry_points.txt +2 -0
- stryx_cli-0.1.0.dist-info/licenses/LICENSE +190 -0
- stryx_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""Injection vulnerability scanner.
|
|
2
|
+
|
|
3
|
+
Generates and tests payloads for SQL injection, NoSQL injection, command injection,
|
|
4
|
+
SSRF, path traversal, XXE, SSTI, LDAP injection, header injection, and open redirect.
|
|
5
|
+
Adapts payloads based on fingerprinted framework.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from urllib.parse import parse_qs, urlencode, urlparse
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from stryx.utils.evidence import Finding, Severity
|
|
17
|
+
from stryx.utils.http_client import HttpClient
|
|
18
|
+
from stryx.utils.logging import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger("scanner.injection")
|
|
21
|
+
|
|
22
|
+
PAYLOADS_DIR = Path(__file__).parent.parent / "payloads"
|
|
23
|
+
SIGNATURES_DIR = Path(__file__).parent.parent / "signatures"
|
|
24
|
+
|
|
25
|
+
# Detection patterns for each injection type
|
|
26
|
+
DETECTION_PATTERNS = {
|
|
27
|
+
"sqli": [
|
|
28
|
+
r"sql syntax",
|
|
29
|
+
r"mysql",
|
|
30
|
+
r"sqlite",
|
|
31
|
+
r"postgresql",
|
|
32
|
+
r"ORA-\d{5}",
|
|
33
|
+
r"Microsoft SQL",
|
|
34
|
+
r"syntax error",
|
|
35
|
+
r"unterminated",
|
|
36
|
+
r"query failed",
|
|
37
|
+
r"database error",
|
|
38
|
+
r"SQLSTATE",
|
|
39
|
+
r"mysql_fetch",
|
|
40
|
+
r"pg_query",
|
|
41
|
+
r"SQLite3::",
|
|
42
|
+
],
|
|
43
|
+
"nosqli": [
|
|
44
|
+
r"MongoError",
|
|
45
|
+
r"MongoServerError",
|
|
46
|
+
r"mongo",
|
|
47
|
+
r"bson",
|
|
48
|
+
r"\$where",
|
|
49
|
+
r"\$gt",
|
|
50
|
+
],
|
|
51
|
+
"cmdi": [
|
|
52
|
+
r"root:",
|
|
53
|
+
r"uid=",
|
|
54
|
+
r"www-data",
|
|
55
|
+
r"/bin/bash",
|
|
56
|
+
r"/bin/sh",
|
|
57
|
+
r"Linux version",
|
|
58
|
+
r"Docker",
|
|
59
|
+
],
|
|
60
|
+
"path_traversal": [
|
|
61
|
+
r"root:",
|
|
62
|
+
r"\[boot loader\]",
|
|
63
|
+
r"/etc/passwd",
|
|
64
|
+
r"/etc/shadow",
|
|
65
|
+
r"root:x:0:0",
|
|
66
|
+
],
|
|
67
|
+
"ssti": [
|
|
68
|
+
r"49", # 7*7
|
|
69
|
+
r"14", # 7+7
|
|
70
|
+
r"<class",
|
|
71
|
+
r"<type",
|
|
72
|
+
r"subclasses",
|
|
73
|
+
],
|
|
74
|
+
"xxe": [
|
|
75
|
+
r"root:",
|
|
76
|
+
r"<!DOCTYPE",
|
|
77
|
+
r"\[xml\]",
|
|
78
|
+
],
|
|
79
|
+
"ssrf": [
|
|
80
|
+
r"Connection refused",
|
|
81
|
+
r"connect to",
|
|
82
|
+
r"No route to host",
|
|
83
|
+
r"timed out",
|
|
84
|
+
r"Failed to connect",
|
|
85
|
+
],
|
|
86
|
+
"ldap": [
|
|
87
|
+
r"ldap",
|
|
88
|
+
r"invalid syntax",
|
|
89
|
+
r"operations error",
|
|
90
|
+
r"protocol error",
|
|
91
|
+
r"no such object",
|
|
92
|
+
r"unwilling to perform",
|
|
93
|
+
],
|
|
94
|
+
"header_injection": [
|
|
95
|
+
r"Content-Type:",
|
|
96
|
+
r"X-",
|
|
97
|
+
r"Location:",
|
|
98
|
+
r"Set-Cookie:",
|
|
99
|
+
r"Transfer-Encoding:",
|
|
100
|
+
],
|
|
101
|
+
"open_redirect": [
|
|
102
|
+
r"redirect",
|
|
103
|
+
r"location",
|
|
104
|
+
r"moved",
|
|
105
|
+
r"301",
|
|
106
|
+
r"302",
|
|
107
|
+
r"303",
|
|
108
|
+
r"307",
|
|
109
|
+
r"308",
|
|
110
|
+
],
|
|
111
|
+
"xss": [
|
|
112
|
+
r"<script>alert\(1\)</script>",
|
|
113
|
+
r"<script>alert\('XSS'\)</script>",
|
|
114
|
+
r"<script>alert\(document\.cookie\)</script>",
|
|
115
|
+
r"<img src=x onerror=alert\(1\)>",
|
|
116
|
+
r"<svg onload=alert\(1\)>",
|
|
117
|
+
r"javascript:alert\(1\)",
|
|
118
|
+
r"<body onload=alert\(1\)>",
|
|
119
|
+
r"<iframe src=\"javascript:alert\(1\)\">",
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _load_payloads(category: str) -> list[str]:
|
|
125
|
+
"""Load payloads from the payloads directory."""
|
|
126
|
+
payload_file = PAYLOADS_DIR / f"{category}.txt"
|
|
127
|
+
if payload_file.exists():
|
|
128
|
+
content = payload_file.read_text()
|
|
129
|
+
return [line.strip() for line in content.splitlines() if line.strip()]
|
|
130
|
+
return []
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _load_framework_signatures() -> dict:
|
|
134
|
+
"""Load framework fingerprint signatures."""
|
|
135
|
+
sig_file = SIGNATURES_DIR / "framework_fingerprints.yaml"
|
|
136
|
+
if sig_file.exists():
|
|
137
|
+
with open(sig_file) as f:
|
|
138
|
+
return yaml.safe_load(f) or {}
|
|
139
|
+
return {}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class InjectionScanner:
|
|
143
|
+
"""Scanner for injection vulnerabilities."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, client: HttpClient):
|
|
146
|
+
self.client = client
|
|
147
|
+
self.framework: str | None = None
|
|
148
|
+
self._signatures = _load_framework_signatures()
|
|
149
|
+
|
|
150
|
+
def fingerprint_framework(self, response_headers: dict[str, str], body: str = "") -> str | None:
|
|
151
|
+
"""Fingerprint the target framework from response."""
|
|
152
|
+
frameworks = self._signatures.get("frameworks", {})
|
|
153
|
+
|
|
154
|
+
for fw_key, fw_data in frameworks.items():
|
|
155
|
+
for header_pattern in fw_data.get("headers", []):
|
|
156
|
+
if ":" in header_pattern:
|
|
157
|
+
h_name, h_value = header_pattern.split(":", 1)
|
|
158
|
+
h_name = h_name.strip().lower()
|
|
159
|
+
h_value = h_value.strip().lower()
|
|
160
|
+
actual_value = response_headers.get(h_name, "").lower()
|
|
161
|
+
if h_value in actual_value:
|
|
162
|
+
self.framework = fw_key
|
|
163
|
+
logger.info(f"Detected framework: {fw_data.get('name', fw_key)}")
|
|
164
|
+
return fw_key
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
async def scan(
|
|
168
|
+
self,
|
|
169
|
+
endpoints: list[str],
|
|
170
|
+
base_url: str,
|
|
171
|
+
max_payloads_per_type: int | None = None,
|
|
172
|
+
) -> list[Finding]:
|
|
173
|
+
"""Run injection tests against all endpoints.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
endpoints: List of endpoint URLs to test.
|
|
177
|
+
base_url: Base URL of the target.
|
|
178
|
+
max_payloads_per_type: Max payloads per injection type (None = all).
|
|
179
|
+
"""
|
|
180
|
+
findings: list[Finding] = []
|
|
181
|
+
logger.info("Running injection scanner")
|
|
182
|
+
|
|
183
|
+
# Test each injection type (all 11)
|
|
184
|
+
injection_types = [
|
|
185
|
+
("sqli", "SQL Injection"),
|
|
186
|
+
("nosqli", "NoSQL Injection"),
|
|
187
|
+
("cmdi", "Command Injection"),
|
|
188
|
+
("path_traversal", "Path Traversal"),
|
|
189
|
+
("ssti", "Server-Side Template Injection"),
|
|
190
|
+
("xxe", "XML External Entity"),
|
|
191
|
+
("ssrf", "Server-Side Request Forgery"),
|
|
192
|
+
("ldap", "LDAP Injection"),
|
|
193
|
+
("header_injection", "Header Injection"),
|
|
194
|
+
("open_redirect", "Open Redirect"),
|
|
195
|
+
("xss", "Cross-Site Scripting (XSS)"),
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
for category, name in injection_types:
|
|
199
|
+
payloads = _load_payloads(category)
|
|
200
|
+
if not payloads:
|
|
201
|
+
logger.warning(f"No payloads found for {category}")
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
# Adapt payloads based on detected framework
|
|
205
|
+
payloads = self._adapt_payloads(category, payloads)
|
|
206
|
+
|
|
207
|
+
# Limit payloads if specified (for testing)
|
|
208
|
+
if max_payloads_per_type is not None:
|
|
209
|
+
payloads = payloads[:max_payloads_per_type]
|
|
210
|
+
|
|
211
|
+
logger.info(f"Testing {name} ({len(payloads)} payloads)")
|
|
212
|
+
|
|
213
|
+
for endpoint in endpoints:
|
|
214
|
+
try:
|
|
215
|
+
result = await self._test_injection(
|
|
216
|
+
endpoint, category, name, payloads
|
|
217
|
+
)
|
|
218
|
+
if result:
|
|
219
|
+
findings.append(result)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.debug(f"Injection test error on {endpoint}: {e}")
|
|
222
|
+
|
|
223
|
+
logger.info(f"Injection scanner found {len(findings)} findings")
|
|
224
|
+
return findings
|
|
225
|
+
|
|
226
|
+
async def _test_injection(
|
|
227
|
+
self,
|
|
228
|
+
endpoint: str,
|
|
229
|
+
category: str,
|
|
230
|
+
name: str,
|
|
231
|
+
payloads: list[str],
|
|
232
|
+
) -> Finding | None:
|
|
233
|
+
"""Test a single endpoint for a specific injection type."""
|
|
234
|
+
parsed = urlparse(endpoint)
|
|
235
|
+
params = parse_qs(parsed.query)
|
|
236
|
+
|
|
237
|
+
# Special handling for header injection
|
|
238
|
+
if category == "header_injection":
|
|
239
|
+
return await self._test_header_injection(endpoint, name, payloads)
|
|
240
|
+
|
|
241
|
+
# Special handling for open redirect
|
|
242
|
+
if category == "open_redirect":
|
|
243
|
+
return await self._test_open_redirect(endpoint, name, payloads)
|
|
244
|
+
|
|
245
|
+
# Standard query parameter testing
|
|
246
|
+
if params:
|
|
247
|
+
for param_name in params:
|
|
248
|
+
for payload in payloads[:10]: # Limit payloads per param
|
|
249
|
+
try:
|
|
250
|
+
# Build test URL
|
|
251
|
+
test_params = dict(params)
|
|
252
|
+
test_params[param_name] = [payload]
|
|
253
|
+
test_url = (
|
|
254
|
+
f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
|
|
255
|
+
f"?{urlencode(test_params, doseq=True)}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
response, evidence = await self.client.get(test_url)
|
|
259
|
+
|
|
260
|
+
# Check for injection indicators
|
|
261
|
+
if self._detect_injection(response.text, category):
|
|
262
|
+
evidence.confidence = 0.8
|
|
263
|
+
evidence.payload = payload
|
|
264
|
+
return Finding(
|
|
265
|
+
title=f"{name} in parameter '{param_name}'",
|
|
266
|
+
severity=Severity.CRITICAL,
|
|
267
|
+
evidence=evidence,
|
|
268
|
+
description=(
|
|
269
|
+
f"Injectable payload in parameter '{param_name}' "
|
|
270
|
+
f"caused a detectable response indicating {name}."
|
|
271
|
+
),
|
|
272
|
+
remediation=(
|
|
273
|
+
f"Sanitize and validate '{param_name}' parameter. "
|
|
274
|
+
f"Use parameterized queries."
|
|
275
|
+
),
|
|
276
|
+
cwe=self._get_cwe(category),
|
|
277
|
+
owasp="A03:2021 - Injection",
|
|
278
|
+
scanner="injection",
|
|
279
|
+
tags=[category, "injection"],
|
|
280
|
+
)
|
|
281
|
+
except Exception:
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
# Test POST body if no query params
|
|
285
|
+
else:
|
|
286
|
+
for payload in payloads[:5]:
|
|
287
|
+
try:
|
|
288
|
+
response, evidence = await self.client.post(
|
|
289
|
+
endpoint,
|
|
290
|
+
body=payload,
|
|
291
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
292
|
+
)
|
|
293
|
+
if self._detect_injection(response.text, category):
|
|
294
|
+
evidence.confidence = 0.7
|
|
295
|
+
evidence.payload = payload
|
|
296
|
+
return Finding(
|
|
297
|
+
title=f"{name} in request body",
|
|
298
|
+
severity=Severity.CRITICAL,
|
|
299
|
+
evidence=evidence,
|
|
300
|
+
description=(
|
|
301
|
+
f"Injectable payload in request body caused a "
|
|
302
|
+
f"detectable response indicating {name}."
|
|
303
|
+
),
|
|
304
|
+
remediation="Sanitize and validate all user input. Use parameterized queries.",
|
|
305
|
+
cwe=self._get_cwe(category),
|
|
306
|
+
owasp="A03:2021 - Injection",
|
|
307
|
+
scanner="injection",
|
|
308
|
+
tags=[category, "injection"],
|
|
309
|
+
)
|
|
310
|
+
except Exception:
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
async def _test_header_injection(
|
|
316
|
+
self, endpoint: str, name: str, payloads: list[str]
|
|
317
|
+
) -> Finding | None:
|
|
318
|
+
"""Test for HTTP header injection."""
|
|
319
|
+
# Header injection payloads are typically CRLF sequences
|
|
320
|
+
for payload in payloads[:10]:
|
|
321
|
+
try:
|
|
322
|
+
# Test via query parameter (most common injection point)
|
|
323
|
+
parsed = urlparse(endpoint)
|
|
324
|
+
test_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}?input={payload}"
|
|
325
|
+
response, evidence = await self.client.get(test_url)
|
|
326
|
+
|
|
327
|
+
# Check if injected headers appear in response
|
|
328
|
+
response_headers = dict(response.headers)
|
|
329
|
+
for header_name in response_headers:
|
|
330
|
+
if header_name.lower().startswith(("x-", "content-", "location")):
|
|
331
|
+
# Check if the header contains our payload
|
|
332
|
+
if payload.strip() in response_headers[header_name]:
|
|
333
|
+
evidence.confidence = 0.8
|
|
334
|
+
evidence.payload = payload
|
|
335
|
+
return Finding(
|
|
336
|
+
title=f"{name} via HTTP header",
|
|
337
|
+
severity=Severity.HIGH,
|
|
338
|
+
evidence=evidence,
|
|
339
|
+
description=(
|
|
340
|
+
"CRLF injection payload caused injected headers "
|
|
341
|
+
"in the response, allowing HTTP header injection."
|
|
342
|
+
),
|
|
343
|
+
remediation="Sanitize user input to prevent CRLF injection.",
|
|
344
|
+
cwe=self._get_cwe("header_injection"),
|
|
345
|
+
owasp="A03:2021 - Injection",
|
|
346
|
+
scanner="injection",
|
|
347
|
+
tags=["header_injection", "injection", "crlf"],
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
continue
|
|
351
|
+
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
async def _test_open_redirect(
|
|
355
|
+
self, endpoint: str, name: str, payloads: list[str]
|
|
356
|
+
) -> Finding | None:
|
|
357
|
+
"""Test for open redirect vulnerabilities."""
|
|
358
|
+
parsed = urlparse(endpoint)
|
|
359
|
+
base = f"{parsed.scheme}://{parsed.netloc}"
|
|
360
|
+
|
|
361
|
+
for payload in payloads[:10]:
|
|
362
|
+
try:
|
|
363
|
+
# Build URL with redirect parameter
|
|
364
|
+
test_url = f"{base}{parsed.path}?next={payload}&redirect={payload}&url={payload}"
|
|
365
|
+
response, evidence = await self.client.get(test_url, follow_redirects=False)
|
|
366
|
+
|
|
367
|
+
# Check for redirects
|
|
368
|
+
if response.status_code in (301, 302, 303, 307, 308):
|
|
369
|
+
location = response.headers.get("location", "")
|
|
370
|
+
# Check if redirect goes to external domain
|
|
371
|
+
if location and not location.startswith("/") and not location.startswith(base):
|
|
372
|
+
evidence.confidence = 0.8
|
|
373
|
+
evidence.payload = payload
|
|
374
|
+
return Finding(
|
|
375
|
+
title=f"{name} to external domain",
|
|
376
|
+
severity=Severity.HIGH,
|
|
377
|
+
evidence=evidence,
|
|
378
|
+
description=(
|
|
379
|
+
f"The application redirects to an external URL: {location}. "
|
|
380
|
+
f"This could be exploited for phishing attacks."
|
|
381
|
+
),
|
|
382
|
+
remediation="Validate redirect URLs against a whitelist of allowed domains.",
|
|
383
|
+
cwe=self._get_cwe("open_redirect"),
|
|
384
|
+
owasp="A01:2021 - Broken Access Control",
|
|
385
|
+
scanner="injection",
|
|
386
|
+
tags=["open_redirect", "injection", "redirect"],
|
|
387
|
+
)
|
|
388
|
+
except Exception:
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
def _adapt_payloads(self, category: str, payloads: list[str]) -> list[str]:
|
|
394
|
+
"""Adapt payloads based on the detected framework.
|
|
395
|
+
|
|
396
|
+
Adds framework-specific payloads and modifies existing ones
|
|
397
|
+
based on the target's SQL dialect and injection modifiers.
|
|
398
|
+
"""
|
|
399
|
+
if not self.framework or not self._signatures:
|
|
400
|
+
return payloads
|
|
401
|
+
|
|
402
|
+
fw_data = self._signatures.get("frameworks", {}).get(self.framework, {})
|
|
403
|
+
if not fw_data:
|
|
404
|
+
return payloads
|
|
405
|
+
|
|
406
|
+
adapted = list(payloads)
|
|
407
|
+
sql_dialect = fw_data.get("sql_dialect", "generic")
|
|
408
|
+
|
|
409
|
+
# Add dialect-specific SQL injection payloads
|
|
410
|
+
if category == "sqli":
|
|
411
|
+
if sql_dialect == "postgres":
|
|
412
|
+
adapted.extend([
|
|
413
|
+
"1; SELECT pg_sleep(5)--",
|
|
414
|
+
"1' AND pg_sleep(5)--",
|
|
415
|
+
"1; WAITFOR DELAY '0:0:5'--",
|
|
416
|
+
])
|
|
417
|
+
elif sql_dialect == "mysql":
|
|
418
|
+
adapted.extend([
|
|
419
|
+
"1; SLEEP(5)--",
|
|
420
|
+
"1' AND SLEEP(5)--",
|
|
421
|
+
"1; SELECT BENCHMARK(10000000,SHA1('test'))--",
|
|
422
|
+
])
|
|
423
|
+
elif sql_dialect == "mssql":
|
|
424
|
+
adapted.extend([
|
|
425
|
+
"1; WAITFOR DELAY '0:0:5'--",
|
|
426
|
+
"1'; WAITFOR DELAY '0:0:5'--",
|
|
427
|
+
])
|
|
428
|
+
|
|
429
|
+
# Add framework-specific SSTI payloads
|
|
430
|
+
if category == "ssti":
|
|
431
|
+
fw_name = fw_data.get("name", "").lower()
|
|
432
|
+
if "flask" in fw_name or "django" in fw_name:
|
|
433
|
+
adapted.extend([
|
|
434
|
+
"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}",
|
|
435
|
+
"{{request.application.__self__._get_data_for_json.__globals__['os'].popen('id').read()}}",
|
|
436
|
+
])
|
|
437
|
+
elif "express" in fw_name or "node" in fw_name:
|
|
438
|
+
adapted.extend([
|
|
439
|
+
"${7*7}",
|
|
440
|
+
"#{7*7}",
|
|
441
|
+
])
|
|
442
|
+
elif "spring" in fw_name or "java" in fw_name:
|
|
443
|
+
adapted.extend([
|
|
444
|
+
"${7*7}",
|
|
445
|
+
"<%= 7*7 %>",
|
|
446
|
+
])
|
|
447
|
+
elif "laravel" in fw_name or "php" in fw_name:
|
|
448
|
+
adapted.extend([
|
|
449
|
+
"{{7*7}}",
|
|
450
|
+
"${7*7}",
|
|
451
|
+
])
|
|
452
|
+
|
|
453
|
+
# Add framework-specific XSS payloads (Jinja2/Twig/Django templates)
|
|
454
|
+
if category == "xss":
|
|
455
|
+
fw_name = fw_data.get("name", "").lower()
|
|
456
|
+
if "flask" in fw_name:
|
|
457
|
+
adapted.extend([
|
|
458
|
+
"{{ '<script>alert(1)</script>' }}",
|
|
459
|
+
"{{ '<img src=x onerror=alert(1)>' }}",
|
|
460
|
+
])
|
|
461
|
+
elif "django" in fw_name:
|
|
462
|
+
adapted.extend([
|
|
463
|
+
"{{ '<script>alert(1)</script>'|safe }}",
|
|
464
|
+
])
|
|
465
|
+
elif "express" in fw_name:
|
|
466
|
+
adapted.extend([
|
|
467
|
+
"<%= '<script>alert(1)</script>' %>",
|
|
468
|
+
"<%- '<script>alert(1)</script>' %>",
|
|
469
|
+
])
|
|
470
|
+
|
|
471
|
+
# Add framework-specific command injection payloads
|
|
472
|
+
if category == "cmdi":
|
|
473
|
+
fw_name = fw_data.get("name", "").lower()
|
|
474
|
+
if "linux" in fw_name or "python" in fw_name or "ruby" in fw_name:
|
|
475
|
+
adapted.extend([
|
|
476
|
+
"`id`",
|
|
477
|
+
"$(id)",
|
|
478
|
+
"; id",
|
|
479
|
+
"| id",
|
|
480
|
+
])
|
|
481
|
+
elif "windows" in fw_name or ".net" in fw_name:
|
|
482
|
+
adapted.extend([
|
|
483
|
+
"& whoami",
|
|
484
|
+
"| whoami",
|
|
485
|
+
"&& whoami",
|
|
486
|
+
])
|
|
487
|
+
|
|
488
|
+
return adapted
|
|
489
|
+
|
|
490
|
+
def _detect_injection(self, response_body: str, category: str) -> bool:
|
|
491
|
+
"""Check response for injection indicators."""
|
|
492
|
+
patterns = DETECTION_PATTERNS.get(category, [])
|
|
493
|
+
body_lower = response_body.lower()
|
|
494
|
+
return any(re.search(p, body_lower, re.IGNORECASE) for p in patterns)
|
|
495
|
+
|
|
496
|
+
def _get_cwe(self, category: str) -> str:
|
|
497
|
+
"""Map injection type to CWE."""
|
|
498
|
+
cwe_map = {
|
|
499
|
+
"sqli": "CWE-89",
|
|
500
|
+
"nosqli": "CWE-943",
|
|
501
|
+
"cmdi": "CWE-78",
|
|
502
|
+
"path_traversal": "CWE-22",
|
|
503
|
+
"ssti": "CWE-96",
|
|
504
|
+
"xxe": "CWE-611",
|
|
505
|
+
"ssrf": "CWE-918",
|
|
506
|
+
"ldap": "CWE-90",
|
|
507
|
+
"header_injection": "CWE-113",
|
|
508
|
+
"open_redirect": "CWE-601",
|
|
509
|
+
"xss": "CWE-79",
|
|
510
|
+
}
|
|
511
|
+
return cwe_map.get(category, "CWE-74")
|