aiptx 2.0.7__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.
Files changed (187) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +46 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/exploit_agent.py +688 -0
  8. aipt_v2/agents/ptt.py +406 -0
  9. aipt_v2/agents/state.py +168 -0
  10. aipt_v2/app.py +957 -0
  11. aipt_v2/browser/__init__.py +31 -0
  12. aipt_v2/browser/automation.py +458 -0
  13. aipt_v2/browser/crawler.py +453 -0
  14. aipt_v2/cli.py +2933 -0
  15. aipt_v2/compliance/__init__.py +71 -0
  16. aipt_v2/compliance/compliance_report.py +449 -0
  17. aipt_v2/compliance/framework_mapper.py +424 -0
  18. aipt_v2/compliance/nist_mapping.py +345 -0
  19. aipt_v2/compliance/owasp_mapping.py +330 -0
  20. aipt_v2/compliance/pci_mapping.py +297 -0
  21. aipt_v2/config.py +341 -0
  22. aipt_v2/core/__init__.py +43 -0
  23. aipt_v2/core/agent.py +630 -0
  24. aipt_v2/core/llm.py +395 -0
  25. aipt_v2/core/memory.py +305 -0
  26. aipt_v2/core/ptt.py +329 -0
  27. aipt_v2/database/__init__.py +14 -0
  28. aipt_v2/database/models.py +232 -0
  29. aipt_v2/database/repository.py +384 -0
  30. aipt_v2/docker/__init__.py +23 -0
  31. aipt_v2/docker/builder.py +260 -0
  32. aipt_v2/docker/manager.py +222 -0
  33. aipt_v2/docker/sandbox.py +371 -0
  34. aipt_v2/evasion/__init__.py +58 -0
  35. aipt_v2/evasion/request_obfuscator.py +272 -0
  36. aipt_v2/evasion/tls_fingerprint.py +285 -0
  37. aipt_v2/evasion/ua_rotator.py +301 -0
  38. aipt_v2/evasion/waf_bypass.py +439 -0
  39. aipt_v2/execution/__init__.py +23 -0
  40. aipt_v2/execution/executor.py +302 -0
  41. aipt_v2/execution/parser.py +544 -0
  42. aipt_v2/execution/terminal.py +337 -0
  43. aipt_v2/health.py +437 -0
  44. aipt_v2/intelligence/__init__.py +194 -0
  45. aipt_v2/intelligence/adaptation.py +474 -0
  46. aipt_v2/intelligence/auth.py +520 -0
  47. aipt_v2/intelligence/chaining.py +775 -0
  48. aipt_v2/intelligence/correlation.py +536 -0
  49. aipt_v2/intelligence/cve_aipt.py +334 -0
  50. aipt_v2/intelligence/cve_info.py +1111 -0
  51. aipt_v2/intelligence/knowledge_graph.py +590 -0
  52. aipt_v2/intelligence/learning.py +626 -0
  53. aipt_v2/intelligence/llm_analyzer.py +502 -0
  54. aipt_v2/intelligence/llm_tool_selector.py +518 -0
  55. aipt_v2/intelligence/payload_generator.py +562 -0
  56. aipt_v2/intelligence/rag.py +239 -0
  57. aipt_v2/intelligence/scope.py +442 -0
  58. aipt_v2/intelligence/searchers/__init__.py +5 -0
  59. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  60. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  61. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  62. aipt_v2/intelligence/tools.json +443 -0
  63. aipt_v2/intelligence/triage.py +670 -0
  64. aipt_v2/interactive_shell.py +559 -0
  65. aipt_v2/interface/__init__.py +5 -0
  66. aipt_v2/interface/cli.py +230 -0
  67. aipt_v2/interface/main.py +501 -0
  68. aipt_v2/interface/tui.py +1276 -0
  69. aipt_v2/interface/utils.py +583 -0
  70. aipt_v2/llm/__init__.py +39 -0
  71. aipt_v2/llm/config.py +26 -0
  72. aipt_v2/llm/llm.py +514 -0
  73. aipt_v2/llm/memory.py +214 -0
  74. aipt_v2/llm/request_queue.py +89 -0
  75. aipt_v2/llm/utils.py +89 -0
  76. aipt_v2/local_tool_installer.py +1467 -0
  77. aipt_v2/models/__init__.py +15 -0
  78. aipt_v2/models/findings.py +295 -0
  79. aipt_v2/models/phase_result.py +224 -0
  80. aipt_v2/models/scan_config.py +207 -0
  81. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  82. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  83. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  84. aipt_v2/monitoring/prometheus.yml +60 -0
  85. aipt_v2/orchestration/__init__.py +52 -0
  86. aipt_v2/orchestration/pipeline.py +398 -0
  87. aipt_v2/orchestration/progress.py +300 -0
  88. aipt_v2/orchestration/scheduler.py +296 -0
  89. aipt_v2/orchestrator.py +2427 -0
  90. aipt_v2/payloads/__init__.py +27 -0
  91. aipt_v2/payloads/cmdi.py +150 -0
  92. aipt_v2/payloads/sqli.py +263 -0
  93. aipt_v2/payloads/ssrf.py +204 -0
  94. aipt_v2/payloads/templates.py +222 -0
  95. aipt_v2/payloads/traversal.py +166 -0
  96. aipt_v2/payloads/xss.py +204 -0
  97. aipt_v2/prompts/__init__.py +60 -0
  98. aipt_v2/proxy/__init__.py +29 -0
  99. aipt_v2/proxy/history.py +352 -0
  100. aipt_v2/proxy/interceptor.py +452 -0
  101. aipt_v2/recon/__init__.py +44 -0
  102. aipt_v2/recon/dns.py +241 -0
  103. aipt_v2/recon/osint.py +367 -0
  104. aipt_v2/recon/subdomain.py +372 -0
  105. aipt_v2/recon/tech_detect.py +311 -0
  106. aipt_v2/reports/__init__.py +17 -0
  107. aipt_v2/reports/generator.py +313 -0
  108. aipt_v2/reports/html_report.py +378 -0
  109. aipt_v2/runtime/__init__.py +53 -0
  110. aipt_v2/runtime/base.py +30 -0
  111. aipt_v2/runtime/docker.py +401 -0
  112. aipt_v2/runtime/local.py +346 -0
  113. aipt_v2/runtime/tool_server.py +205 -0
  114. aipt_v2/runtime/vps.py +830 -0
  115. aipt_v2/scanners/__init__.py +28 -0
  116. aipt_v2/scanners/base.py +273 -0
  117. aipt_v2/scanners/nikto.py +244 -0
  118. aipt_v2/scanners/nmap.py +402 -0
  119. aipt_v2/scanners/nuclei.py +273 -0
  120. aipt_v2/scanners/web.py +454 -0
  121. aipt_v2/scripts/security_audit.py +366 -0
  122. aipt_v2/setup_wizard.py +941 -0
  123. aipt_v2/skills/__init__.py +80 -0
  124. aipt_v2/skills/agents/__init__.py +14 -0
  125. aipt_v2/skills/agents/api_tester.py +706 -0
  126. aipt_v2/skills/agents/base.py +477 -0
  127. aipt_v2/skills/agents/code_review.py +459 -0
  128. aipt_v2/skills/agents/security_agent.py +336 -0
  129. aipt_v2/skills/agents/web_pentest.py +818 -0
  130. aipt_v2/skills/prompts/__init__.py +647 -0
  131. aipt_v2/system_detector.py +539 -0
  132. aipt_v2/telemetry/__init__.py +7 -0
  133. aipt_v2/telemetry/tracer.py +347 -0
  134. aipt_v2/terminal/__init__.py +28 -0
  135. aipt_v2/terminal/executor.py +400 -0
  136. aipt_v2/terminal/sandbox.py +350 -0
  137. aipt_v2/tools/__init__.py +44 -0
  138. aipt_v2/tools/active_directory/__init__.py +78 -0
  139. aipt_v2/tools/active_directory/ad_config.py +238 -0
  140. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  141. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  142. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  143. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  144. aipt_v2/tools/agents_graph/__init__.py +19 -0
  145. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  146. aipt_v2/tools/api_security/__init__.py +76 -0
  147. aipt_v2/tools/api_security/api_discovery.py +608 -0
  148. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  149. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  150. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  151. aipt_v2/tools/browser/__init__.py +5 -0
  152. aipt_v2/tools/browser/browser_actions.py +238 -0
  153. aipt_v2/tools/browser/browser_instance.py +535 -0
  154. aipt_v2/tools/browser/tab_manager.py +344 -0
  155. aipt_v2/tools/cloud/__init__.py +70 -0
  156. aipt_v2/tools/cloud/cloud_config.py +273 -0
  157. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  158. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  159. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  160. aipt_v2/tools/executor.py +307 -0
  161. aipt_v2/tools/parser.py +408 -0
  162. aipt_v2/tools/proxy/__init__.py +5 -0
  163. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  164. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  165. aipt_v2/tools/registry.py +196 -0
  166. aipt_v2/tools/scanners/__init__.py +343 -0
  167. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  168. aipt_v2/tools/scanners/burp_tool.py +631 -0
  169. aipt_v2/tools/scanners/config.py +156 -0
  170. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  171. aipt_v2/tools/scanners/zap_tool.py +612 -0
  172. aipt_v2/tools/terminal/__init__.py +5 -0
  173. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  174. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  175. aipt_v2/tools/terminal/terminal_session.py +449 -0
  176. aipt_v2/tools/tool_processing.py +108 -0
  177. aipt_v2/utils/__init__.py +17 -0
  178. aipt_v2/utils/logging.py +202 -0
  179. aipt_v2/utils/model_manager.py +187 -0
  180. aipt_v2/utils/searchers/__init__.py +269 -0
  181. aipt_v2/verify_install.py +793 -0
  182. aiptx-2.0.7.dist-info/METADATA +345 -0
  183. aiptx-2.0.7.dist-info/RECORD +187 -0
  184. aiptx-2.0.7.dist-info/WHEEL +5 -0
  185. aiptx-2.0.7.dist-info/entry_points.txt +7 -0
  186. aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
  187. aiptx-2.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,761 @@
1
+ """
2
+ OpenAPI/Swagger Security Fuzzer
3
+
4
+ Comprehensive REST API security testing based on OpenAPI specifications:
5
+ - Automatic endpoint discovery from OpenAPI/Swagger specs
6
+ - Parameter fuzzing (path, query, header, body)
7
+ - Authentication bypass testing
8
+ - BOLA/IDOR detection
9
+ - Mass assignment vulnerabilities
10
+ - Rate limiting bypass
11
+
12
+ References:
13
+ - OWASP API Security Top 10
14
+ - https://swagger.io/specification/
15
+
16
+ Usage:
17
+ from aipt_v2.tools.api_security import OpenAPIFuzzer
18
+
19
+ fuzzer = OpenAPIFuzzer("https://api.target.com", spec_path="openapi.yaml")
20
+ findings = await fuzzer.fuzz()
21
+ """
22
+
23
+ import asyncio
24
+ import json
25
+ import re
26
+ import random
27
+ import string
28
+ from dataclasses import dataclass, field
29
+ from datetime import datetime, timezone
30
+ from pathlib import Path
31
+ from typing import List, Dict, Any, Optional, Union
32
+ from urllib.parse import urljoin, urlencode
33
+
34
+ try:
35
+ import aiohttp
36
+ except ImportError:
37
+ aiohttp = None
38
+
39
+ try:
40
+ import yaml
41
+ except ImportError:
42
+ yaml = None
43
+
44
+
45
+ @dataclass
46
+ class OpenAPIConfig:
47
+ """OpenAPI fuzzer configuration."""
48
+ base_url: str
49
+ spec_path: Optional[str] = None
50
+ spec_url: Optional[str] = None
51
+ spec_data: Optional[Dict] = None
52
+
53
+ # Authentication
54
+ auth_token: str = ""
55
+ auth_header: str = "Authorization"
56
+ api_key: str = ""
57
+ api_key_header: str = "X-API-Key"
58
+
59
+ # Fuzzing options
60
+ fuzz_parameters: bool = True
61
+ fuzz_bodies: bool = True
62
+ test_bola: bool = True # Broken Object Level Authorization
63
+ test_mass_assignment: bool = True
64
+ test_rate_limit: bool = True
65
+ test_auth_bypass: bool = True
66
+
67
+ # Limits
68
+ max_requests_per_endpoint: int = 10
69
+ timeout: int = 30
70
+ delay_ms: int = 100 # Delay between requests
71
+
72
+ # Headers
73
+ headers: Dict[str, str] = field(default_factory=dict)
74
+
75
+
76
+ @dataclass
77
+ class OpenAPIFinding:
78
+ """OpenAPI security finding."""
79
+ vulnerability: str
80
+ severity: str
81
+ endpoint: str
82
+ method: str
83
+ description: str
84
+ evidence: str
85
+ remediation: str
86
+ parameter: str = ""
87
+ payload: str = ""
88
+ response_code: int = 0
89
+ timestamp: str = ""
90
+ cwe: str = ""
91
+
92
+ def __post_init__(self):
93
+ if not self.timestamp:
94
+ self.timestamp = datetime.now(timezone.utc).isoformat()
95
+
96
+
97
+ @dataclass
98
+ class OpenAPIEndpoint:
99
+ """Parsed OpenAPI endpoint."""
100
+ path: str
101
+ method: str
102
+ operation_id: str
103
+ summary: str
104
+ parameters: List[Dict]
105
+ request_body: Optional[Dict]
106
+ responses: Dict
107
+ security: List[Dict]
108
+ tags: List[str]
109
+
110
+
111
+ @dataclass
112
+ class OpenAPIFuzzResult:
113
+ """Result of OpenAPI fuzzing."""
114
+ base_url: str
115
+ status: str
116
+ started_at: str
117
+ finished_at: str
118
+ duration: float
119
+ endpoints_tested: int
120
+ requests_made: int
121
+ findings: List[OpenAPIFinding]
122
+ spec_info: Dict[str, Any]
123
+ metadata: Dict[str, Any] = field(default_factory=dict)
124
+
125
+
126
+ class OpenAPIFuzzer:
127
+ """
128
+ OpenAPI/Swagger Security Fuzzer.
129
+
130
+ Parses OpenAPI specifications and performs comprehensive
131
+ security testing on discovered endpoints.
132
+ """
133
+
134
+ # Fuzzing payloads by type
135
+ SQLI_PAYLOADS = ["'", "\"", "' OR '1'='1", "1; DROP TABLE users--", "admin'--"]
136
+ XSS_PAYLOADS = ["<script>alert(1)</script>", "javascript:alert(1)", "<img onerror=alert(1)>"]
137
+ PATH_TRAVERSAL = ["../../../etc/passwd", "..\\..\\..\\windows\\system32\\config\\sam"]
138
+ COMMAND_INJECTION = ["; ls -la", "| cat /etc/passwd", "`whoami`", "$(id)"]
139
+ NOSQL_PAYLOADS = ['{"$gt": ""}', '{"$ne": null}', '{"$regex": ".*"}']
140
+
141
+ # BOLA test IDs
142
+ BOLA_IDS = ["1", "0", "-1", "admin", "999999", "../1", "1 OR 1=1"]
143
+
144
+ def __init__(self, base_url: str, config: Optional[OpenAPIConfig] = None, **kwargs):
145
+ """
146
+ Initialize OpenAPI fuzzer.
147
+
148
+ Args:
149
+ base_url: Base API URL
150
+ config: Fuzzer configuration
151
+ **kwargs: Additional config options
152
+ """
153
+ self.base_url = base_url.rstrip("/")
154
+ self.config = config or OpenAPIConfig(
155
+ base_url=base_url,
156
+ spec_path=kwargs.get("spec_path"),
157
+ spec_url=kwargs.get("spec_url")
158
+ )
159
+ self.spec: Dict = {}
160
+ self.endpoints: List[OpenAPIEndpoint] = []
161
+ self.findings: List[OpenAPIFinding] = []
162
+ self.requests_made = 0
163
+
164
+ def _get_headers(self) -> Dict[str, str]:
165
+ """Build request headers."""
166
+ headers = {
167
+ "Content-Type": "application/json",
168
+ "Accept": "application/json",
169
+ "User-Agent": "AIPTX-OpenAPI-Fuzzer/1.0"
170
+ }
171
+ headers.update(self.config.headers)
172
+
173
+ if self.config.auth_token:
174
+ headers[self.config.auth_header] = f"Bearer {self.config.auth_token}"
175
+
176
+ if self.config.api_key:
177
+ headers[self.config.api_key_header] = self.config.api_key
178
+
179
+ return headers
180
+
181
+ async def load_spec(self) -> bool:
182
+ """Load and parse OpenAPI specification."""
183
+ spec_data = None
184
+
185
+ # Try loading from data
186
+ if self.config.spec_data:
187
+ spec_data = self.config.spec_data
188
+
189
+ # Try loading from file
190
+ elif self.config.spec_path:
191
+ path = Path(self.config.spec_path)
192
+ if path.exists():
193
+ content = path.read_text()
194
+ if path.suffix in [".yaml", ".yml"]:
195
+ if yaml:
196
+ spec_data = yaml.safe_load(content)
197
+ else:
198
+ raise ImportError("PyYAML required for YAML specs. Install with: pip install pyyaml")
199
+ else:
200
+ spec_data = json.loads(content)
201
+
202
+ # Try loading from URL
203
+ elif self.config.spec_url:
204
+ if aiohttp:
205
+ try:
206
+ async with aiohttp.ClientSession() as session:
207
+ async with session.get(
208
+ self.config.spec_url,
209
+ headers=self._get_headers(),
210
+ ssl=False
211
+ ) as response:
212
+ text = await response.text()
213
+ if "yaml" in self.config.spec_url or "yml" in self.config.spec_url:
214
+ if yaml:
215
+ spec_data = yaml.safe_load(text)
216
+ else:
217
+ spec_data = json.loads(text)
218
+ except Exception as e:
219
+ print(f"[!] Error loading spec from URL: {e}")
220
+
221
+ # Try common spec locations
222
+ if not spec_data:
223
+ common_paths = [
224
+ "/openapi.json", "/swagger.json", "/api-docs",
225
+ "/openapi.yaml", "/swagger.yaml",
226
+ "/v2/api-docs", "/v3/api-docs"
227
+ ]
228
+
229
+ if aiohttp:
230
+ async with aiohttp.ClientSession() as session:
231
+ for path in common_paths:
232
+ try:
233
+ url = urljoin(self.base_url, path)
234
+ async with session.get(url, ssl=False, timeout=10) as response:
235
+ if response.status == 200:
236
+ text = await response.text()
237
+ try:
238
+ spec_data = json.loads(text)
239
+ print(f"[*] Found OpenAPI spec at {path}")
240
+ break
241
+ except json.JSONDecodeError:
242
+ if yaml:
243
+ spec_data = yaml.safe_load(text)
244
+ print(f"[*] Found OpenAPI spec at {path}")
245
+ break
246
+ except Exception:
247
+ continue
248
+
249
+ if spec_data:
250
+ self.spec = spec_data
251
+ self._parse_endpoints()
252
+ return True
253
+
254
+ return False
255
+
256
+ def _parse_endpoints(self):
257
+ """Parse endpoints from OpenAPI spec."""
258
+ self.endpoints = []
259
+
260
+ # Get paths from spec
261
+ paths = self.spec.get("paths", {})
262
+
263
+ for path, methods in paths.items():
264
+ for method, operation in methods.items():
265
+ if method.upper() not in ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]:
266
+ continue
267
+
268
+ # Parse parameters
269
+ parameters = []
270
+ params = operation.get("parameters", []) + methods.get("parameters", [])
271
+ for param in params:
272
+ # Handle $ref
273
+ if "$ref" in param:
274
+ ref_path = param["$ref"].split("/")[-1]
275
+ param = self.spec.get("components", {}).get("parameters", {}).get(ref_path, param)
276
+
277
+ parameters.append({
278
+ "name": param.get("name", ""),
279
+ "in": param.get("in", "query"),
280
+ "required": param.get("required", False),
281
+ "schema": param.get("schema", {}),
282
+ "type": param.get("schema", {}).get("type", "string")
283
+ })
284
+
285
+ # Parse request body
286
+ request_body = None
287
+ if "requestBody" in operation:
288
+ rb = operation["requestBody"]
289
+ content = rb.get("content", {})
290
+ if "application/json" in content:
291
+ schema = content["application/json"].get("schema", {})
292
+ request_body = {
293
+ "required": rb.get("required", False),
294
+ "schema": schema
295
+ }
296
+
297
+ endpoint = OpenAPIEndpoint(
298
+ path=path,
299
+ method=method.upper(),
300
+ operation_id=operation.get("operationId", ""),
301
+ summary=operation.get("summary", ""),
302
+ parameters=parameters,
303
+ request_body=request_body,
304
+ responses=operation.get("responses", {}),
305
+ security=operation.get("security", []),
306
+ tags=operation.get("tags", [])
307
+ )
308
+
309
+ self.endpoints.append(endpoint)
310
+
311
+ async def _send_request(
312
+ self,
313
+ method: str,
314
+ path: str,
315
+ params: Optional[Dict] = None,
316
+ body: Optional[Dict] = None,
317
+ headers: Optional[Dict] = None
318
+ ) -> Dict[str, Any]:
319
+ """Send HTTP request and return response."""
320
+ if aiohttp is None:
321
+ raise ImportError("aiohttp required. Install with: pip install aiohttp")
322
+
323
+ url = urljoin(self.base_url, path)
324
+ if params:
325
+ url = f"{url}?{urlencode(params)}"
326
+
327
+ req_headers = self._get_headers()
328
+ if headers:
329
+ req_headers.update(headers)
330
+
331
+ try:
332
+ await asyncio.sleep(self.config.delay_ms / 1000)
333
+ self.requests_made += 1
334
+
335
+ async with aiohttp.ClientSession() as session:
336
+ async with session.request(
337
+ method,
338
+ url,
339
+ json=body if body else None,
340
+ headers=req_headers,
341
+ timeout=aiohttp.ClientTimeout(total=self.config.timeout),
342
+ ssl=False
343
+ ) as response:
344
+ text = await response.text()
345
+ try:
346
+ data = json.loads(text) if text else {}
347
+ except json.JSONDecodeError:
348
+ data = {"raw": text}
349
+
350
+ return {
351
+ "status": response.status,
352
+ "headers": dict(response.headers),
353
+ "data": data,
354
+ "url": str(response.url)
355
+ }
356
+ except Exception as e:
357
+ return {"error": str(e)}
358
+
359
+ async def fuzz_endpoint(self, endpoint: OpenAPIEndpoint) -> List[OpenAPIFinding]:
360
+ """Fuzz a single endpoint."""
361
+ findings = []
362
+
363
+ # Fuzz path parameters
364
+ path = endpoint.path
365
+ path_params = re.findall(r"\{(\w+)\}", path)
366
+
367
+ for param in path_params:
368
+ for payload in self.SQLI_PAYLOADS + self.PATH_TRAVERSAL:
369
+ test_path = path.replace(f"{{{param}}}", payload)
370
+ response = await self._send_request(endpoint.method, test_path)
371
+
372
+ if self._check_sqli_response(response):
373
+ findings.append(OpenAPIFinding(
374
+ vulnerability="SQL Injection in Path Parameter",
375
+ severity="critical",
376
+ endpoint=endpoint.path,
377
+ method=endpoint.method,
378
+ parameter=param,
379
+ payload=payload,
380
+ description=f"Path parameter '{param}' appears vulnerable to SQL injection",
381
+ evidence=f"Response indicates SQL error or unexpected behavior",
382
+ response_code=response.get("status", 0),
383
+ remediation="Use parameterized queries and input validation",
384
+ cwe="CWE-89"
385
+ ))
386
+ break
387
+
388
+ # Fuzz query parameters
389
+ if self.config.fuzz_parameters:
390
+ for param in endpoint.parameters:
391
+ if param["in"] == "query":
392
+ for payload in self.SQLI_PAYLOADS + self.XSS_PAYLOADS:
393
+ path_with_id = self._replace_path_params(endpoint.path)
394
+ response = await self._send_request(
395
+ endpoint.method,
396
+ path_with_id,
397
+ params={param["name"]: payload}
398
+ )
399
+
400
+ if self._check_sqli_response(response):
401
+ findings.append(OpenAPIFinding(
402
+ vulnerability="SQL Injection in Query Parameter",
403
+ severity="critical",
404
+ endpoint=endpoint.path,
405
+ method=endpoint.method,
406
+ parameter=param["name"],
407
+ payload=payload,
408
+ description=f"Query parameter '{param['name']}' vulnerable to injection",
409
+ evidence="Response indicates injection vulnerability",
410
+ response_code=response.get("status", 0),
411
+ remediation="Validate and sanitize all input",
412
+ cwe="CWE-89"
413
+ ))
414
+ break
415
+
416
+ if self._check_xss_response(response, payload):
417
+ findings.append(OpenAPIFinding(
418
+ vulnerability="Reflected XSS in Query Parameter",
419
+ severity="high",
420
+ endpoint=endpoint.path,
421
+ method=endpoint.method,
422
+ parameter=param["name"],
423
+ payload=payload,
424
+ description=f"Query parameter '{param['name']}' reflects XSS payload",
425
+ evidence="XSS payload reflected in response",
426
+ response_code=response.get("status", 0),
427
+ remediation="Encode output and validate input",
428
+ cwe="CWE-79"
429
+ ))
430
+ break
431
+
432
+ # Fuzz request body
433
+ if self.config.fuzz_bodies and endpoint.request_body:
434
+ findings.extend(await self._fuzz_body(endpoint))
435
+
436
+ return findings
437
+
438
+ def _replace_path_params(self, path: str) -> str:
439
+ """Replace path parameters with test values."""
440
+ return re.sub(r"\{(\w+)\}", "1", path)
441
+
442
+ def _check_sqli_response(self, response: Dict) -> bool:
443
+ """Check if response indicates SQL injection."""
444
+ if "error" in response:
445
+ return False
446
+
447
+ data_str = json.dumps(response.get("data", {})).lower()
448
+ sql_indicators = [
449
+ "sql syntax", "mysql", "postgresql", "sqlite", "oracle",
450
+ "syntax error", "unclosed quotation", "unterminated",
451
+ "ORA-", "PG::", "SQLSTATE", "SQL Server"
452
+ ]
453
+ return any(ind.lower() in data_str for ind in sql_indicators)
454
+
455
+ def _check_xss_response(self, response: Dict, payload: str) -> bool:
456
+ """Check if XSS payload is reflected."""
457
+ if "error" in response:
458
+ return False
459
+
460
+ data_str = json.dumps(response.get("data", {}))
461
+ return payload in data_str
462
+
463
+ async def _fuzz_body(self, endpoint: OpenAPIEndpoint) -> List[OpenAPIFinding]:
464
+ """Fuzz request body parameters."""
465
+ findings = []
466
+
467
+ if not endpoint.request_body:
468
+ return findings
469
+
470
+ schema = endpoint.request_body.get("schema", {})
471
+ properties = schema.get("properties", {})
472
+
473
+ for prop_name, prop_schema in properties.items():
474
+ prop_type = prop_schema.get("type", "string")
475
+
476
+ # Test injection in string fields
477
+ if prop_type == "string":
478
+ for payload in self.SQLI_PAYLOADS[:3]:
479
+ body = {prop_name: payload}
480
+ path = self._replace_path_params(endpoint.path)
481
+ response = await self._send_request(endpoint.method, path, body=body)
482
+
483
+ if self._check_sqli_response(response):
484
+ findings.append(OpenAPIFinding(
485
+ vulnerability="SQL Injection in Request Body",
486
+ severity="critical",
487
+ endpoint=endpoint.path,
488
+ method=endpoint.method,
489
+ parameter=prop_name,
490
+ payload=payload,
491
+ description=f"Body parameter '{prop_name}' vulnerable to SQL injection",
492
+ evidence="SQL error detected in response",
493
+ response_code=response.get("status", 0),
494
+ remediation="Use parameterized queries",
495
+ cwe="CWE-89"
496
+ ))
497
+ break
498
+
499
+ return findings
500
+
501
+ async def test_bola(self) -> List[OpenAPIFinding]:
502
+ """Test for Broken Object Level Authorization (BOLA/IDOR)."""
503
+ findings = []
504
+
505
+ for endpoint in self.endpoints:
506
+ # Look for ID parameters in path
507
+ if "{id}" in endpoint.path or any("{" in endpoint.path for _ in [1]):
508
+ for test_id in self.BOLA_IDS:
509
+ path = re.sub(r"\{[^}]+\}", test_id, endpoint.path)
510
+ response = await self._send_request(endpoint.method, path)
511
+
512
+ if response.get("status") == 200 and "error" not in response:
513
+ data = response.get("data", {})
514
+ if data and data != {"raw": ""}:
515
+ findings.append(OpenAPIFinding(
516
+ vulnerability="Potential BOLA/IDOR",
517
+ severity="high",
518
+ endpoint=endpoint.path,
519
+ method=endpoint.method,
520
+ payload=test_id,
521
+ description=f"Endpoint may be vulnerable to BOLA with ID: {test_id}",
522
+ evidence=f"Received 200 OK for ID: {test_id}",
523
+ response_code=200,
524
+ remediation="Implement proper authorization checks for all resources",
525
+ cwe="CWE-639"
526
+ ))
527
+ break
528
+
529
+ return findings
530
+
531
+ async def test_auth_bypass(self) -> List[OpenAPIFinding]:
532
+ """Test for authentication bypass."""
533
+ findings = []
534
+
535
+ # Save current auth
536
+ orig_token = self.config.auth_token
537
+ orig_key = self.config.api_key
538
+
539
+ # Test without auth
540
+ self.config.auth_token = ""
541
+ self.config.api_key = ""
542
+
543
+ for endpoint in self.endpoints:
544
+ if endpoint.security: # Should require auth
545
+ path = self._replace_path_params(endpoint.path)
546
+ response = await self._send_request(endpoint.method, path)
547
+
548
+ if response.get("status") == 200:
549
+ findings.append(OpenAPIFinding(
550
+ vulnerability="Authentication Bypass",
551
+ severity="critical",
552
+ endpoint=endpoint.path,
553
+ method=endpoint.method,
554
+ description="Endpoint accessible without authentication",
555
+ evidence=f"Received 200 OK without auth token",
556
+ response_code=200,
557
+ remediation="Enforce authentication on all protected endpoints",
558
+ cwe="CWE-306"
559
+ ))
560
+
561
+ # Restore auth
562
+ self.config.auth_token = orig_token
563
+ self.config.api_key = orig_key
564
+
565
+ return findings
566
+
567
+ async def test_rate_limiting(self) -> List[OpenAPIFinding]:
568
+ """Test for missing rate limiting."""
569
+ findings = []
570
+
571
+ # Pick a GET endpoint
572
+ get_endpoints = [e for e in self.endpoints if e.method == "GET"]
573
+ if not get_endpoints:
574
+ return findings
575
+
576
+ endpoint = get_endpoints[0]
577
+ path = self._replace_path_params(endpoint.path)
578
+
579
+ # Send rapid requests
580
+ success_count = 0
581
+ for _ in range(20):
582
+ response = await self._send_request("GET", path)
583
+ if response.get("status") == 200:
584
+ success_count += 1
585
+ elif response.get("status") == 429:
586
+ return findings # Rate limiting detected
587
+
588
+ if success_count >= 18:
589
+ findings.append(OpenAPIFinding(
590
+ vulnerability="Missing Rate Limiting",
591
+ severity="medium",
592
+ endpoint=endpoint.path,
593
+ method="GET",
594
+ description="API lacks rate limiting protection",
595
+ evidence=f"{success_count}/20 rapid requests succeeded",
596
+ response_code=200,
597
+ remediation="Implement rate limiting to prevent abuse",
598
+ cwe="CWE-770"
599
+ ))
600
+
601
+ return findings
602
+
603
+ async def test_mass_assignment(self) -> List[OpenAPIFinding]:
604
+ """Test for mass assignment vulnerabilities."""
605
+ findings = []
606
+
607
+ # Look for POST/PUT/PATCH endpoints
608
+ for endpoint in self.endpoints:
609
+ if endpoint.method not in ["POST", "PUT", "PATCH"]:
610
+ continue
611
+ if not endpoint.request_body:
612
+ continue
613
+
614
+ # Try adding extra fields
615
+ extra_fields = {
616
+ "role": "admin",
617
+ "isAdmin": True,
618
+ "admin": True,
619
+ "permissions": ["admin"],
620
+ "is_superuser": True,
621
+ "privilege": "admin"
622
+ }
623
+
624
+ path = self._replace_path_params(endpoint.path)
625
+
626
+ for field_name, field_value in extra_fields.items():
627
+ body = {field_name: field_value}
628
+ response = await self._send_request(endpoint.method, path, body=body)
629
+
630
+ if response.get("status") in [200, 201]:
631
+ data = response.get("data", {})
632
+ if isinstance(data, dict) and field_name in str(data):
633
+ findings.append(OpenAPIFinding(
634
+ vulnerability="Potential Mass Assignment",
635
+ severity="high",
636
+ endpoint=endpoint.path,
637
+ method=endpoint.method,
638
+ parameter=field_name,
639
+ description=f"API accepts undocumented field: {field_name}",
640
+ evidence=f"Field '{field_name}' was accepted in request",
641
+ response_code=response.get("status", 0),
642
+ remediation="Implement allowlist for acceptable fields",
643
+ cwe="CWE-915"
644
+ ))
645
+
646
+ return findings
647
+
648
+ async def fuzz(self) -> OpenAPIFuzzResult:
649
+ """
650
+ Run full OpenAPI fuzzing scan.
651
+
652
+ Returns:
653
+ OpenAPIFuzzResult with all findings
654
+ """
655
+ started_at = datetime.now(timezone.utc).isoformat()
656
+ start_time = asyncio.get_event_loop().time()
657
+
658
+ # Load spec
659
+ if not self.spec:
660
+ spec_loaded = await self.load_spec()
661
+ if not spec_loaded:
662
+ return OpenAPIFuzzResult(
663
+ base_url=self.base_url,
664
+ status="failed",
665
+ started_at=started_at,
666
+ finished_at=datetime.now(timezone.utc).isoformat(),
667
+ duration=0,
668
+ endpoints_tested=0,
669
+ requests_made=0,
670
+ findings=[],
671
+ spec_info={},
672
+ metadata={"error": "Could not load OpenAPI specification"}
673
+ )
674
+
675
+ findings = []
676
+
677
+ # Fuzz each endpoint
678
+ for endpoint in self.endpoints:
679
+ endpoint_findings = await self.fuzz_endpoint(endpoint)
680
+ findings.extend(endpoint_findings)
681
+
682
+ # Run additional tests
683
+ if self.config.test_bola:
684
+ findings.extend(await self.test_bola())
685
+
686
+ if self.config.test_auth_bypass:
687
+ findings.extend(await self.test_auth_bypass())
688
+
689
+ if self.config.test_rate_limit:
690
+ findings.extend(await self.test_rate_limiting())
691
+
692
+ if self.config.test_mass_assignment:
693
+ findings.extend(await self.test_mass_assignment())
694
+
695
+ finished_at = datetime.now(timezone.utc).isoformat()
696
+ duration = asyncio.get_event_loop().time() - start_time
697
+
698
+ # Spec info
699
+ spec_info = {
700
+ "title": self.spec.get("info", {}).get("title", ""),
701
+ "version": self.spec.get("info", {}).get("version", ""),
702
+ "openapi_version": self.spec.get("openapi", self.spec.get("swagger", "")),
703
+ "endpoints_count": len(self.endpoints),
704
+ "servers": self.spec.get("servers", [])
705
+ }
706
+
707
+ return OpenAPIFuzzResult(
708
+ base_url=self.base_url,
709
+ status="completed",
710
+ started_at=started_at,
711
+ finished_at=finished_at,
712
+ duration=duration,
713
+ endpoints_tested=len(self.endpoints),
714
+ requests_made=self.requests_made,
715
+ findings=findings,
716
+ spec_info=spec_info,
717
+ metadata={
718
+ "config": {
719
+ "fuzz_parameters": self.config.fuzz_parameters,
720
+ "fuzz_bodies": self.config.fuzz_bodies,
721
+ "test_bola": self.config.test_bola,
722
+ "test_auth_bypass": self.config.test_auth_bypass
723
+ }
724
+ }
725
+ )
726
+
727
+
728
+ # Convenience function
729
+ async def fuzz_openapi(
730
+ base_url: str,
731
+ spec_path: Optional[str] = None,
732
+ spec_url: Optional[str] = None,
733
+ auth_token: Optional[str] = None,
734
+ full_scan: bool = True
735
+ ) -> OpenAPIFuzzResult:
736
+ """
737
+ Quick OpenAPI fuzzing scan.
738
+
739
+ Args:
740
+ base_url: Base API URL
741
+ spec_path: Path to OpenAPI spec file
742
+ spec_url: URL to OpenAPI spec
743
+ auth_token: Bearer token for authentication
744
+ full_scan: Run all tests if True
745
+
746
+ Returns:
747
+ OpenAPIFuzzResult
748
+ """
749
+ config = OpenAPIConfig(
750
+ base_url=base_url,
751
+ spec_path=spec_path,
752
+ spec_url=spec_url,
753
+ auth_token=auth_token or "",
754
+ test_bola=full_scan,
755
+ test_mass_assignment=full_scan,
756
+ test_rate_limit=full_scan,
757
+ test_auth_bypass=full_scan
758
+ )
759
+
760
+ fuzzer = OpenAPIFuzzer(base_url, config)
761
+ return await fuzzer.fuzz()