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,688 @@
1
+ """
2
+ AIPT Exploitation Reasoning Agent
3
+
4
+ An LLM-powered agent that reasons through exploitation step-by-step:
5
+ - Analyzes vulnerability context
6
+ - Plans exploitation strategy
7
+ - Executes actions and learns from results
8
+ - Adapts approach based on feedback
9
+
10
+ This provides intelligent, adaptive exploitation beyond script-based attacks.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import json
16
+ import logging
17
+ import os
18
+ from dataclasses import dataclass, field
19
+ from datetime import datetime
20
+ from enum import Enum
21
+ from typing import Any, Callable, Optional
22
+
23
+ from aipt_v2.models.findings import Finding, Severity, VulnerabilityType
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ EXPLOIT_REASONING_PROMPT = """You are an expert penetration tester exploiting a vulnerability.
29
+
30
+ ## Vulnerability Details
31
+ - **Title**: {title}
32
+ - **Type**: {vuln_type}
33
+ - **Severity**: {severity}
34
+ - **URL**: {url}
35
+ - **Parameter**: {parameter}
36
+ - **Evidence**: {evidence}
37
+
38
+ ## Environment Context
39
+ - **Technology Stack**: {tech_stack}
40
+ - **WAF Detected**: {waf}
41
+ - **Authentication**: {auth_status}
42
+
43
+ ## Exploitation History
44
+ {history}
45
+
46
+ ## Your Objective
47
+ {objective}
48
+
49
+ ## Available Actions
50
+ You can perform ONE of these actions:
51
+
52
+ 1. **send_request**: Send an HTTP request
53
+ - method: GET|POST|PUT|DELETE
54
+ - url: target URL
55
+ - headers: dict of headers
56
+ - body: request body
57
+ - params: query parameters
58
+
59
+ 2. **test_payload**: Test a specific payload
60
+ - payload: the payload string
61
+ - encoding: none|url|double_url|base64|unicode
62
+
63
+ 3. **analyze_response**: Analyze the last response
64
+ - check_for: what to look for (error, success_indicator, data)
65
+
66
+ 4. **extract_data**: Extract data from response
67
+ - pattern: regex pattern to extract
68
+ - data_type: credentials|tokens|database|files
69
+
70
+ 5. **escalate**: Attempt privilege escalation
71
+ - technique: the escalation technique
72
+
73
+ 6. **conclude**: End exploitation with result
74
+ - success: true|false
75
+ - evidence: proof of exploitation
76
+ - impact: description of achieved impact
77
+
78
+ ## Think Step by Step
79
+ 1. What is the current state of the exploitation?
80
+ 2. What obstacles exist (WAF, auth, etc.)?
81
+ 3. What should you try next?
82
+ 4. What do you expect to happen?
83
+
84
+ ## Response Format (JSON)
85
+ ```json
86
+ {{
87
+ "reasoning": "Your step-by-step reasoning",
88
+ "action": "action_name",
89
+ "parameters": {{
90
+ "param1": "value1"
91
+ }},
92
+ "expected_outcome": "What you expect to happen",
93
+ "fallback_plan": "What to try if this fails"
94
+ }}
95
+ ```"""
96
+
97
+
98
+ class ExploitAction(Enum):
99
+ """Actions the exploit agent can take."""
100
+ SEND_REQUEST = "send_request"
101
+ TEST_PAYLOAD = "test_payload"
102
+ ANALYZE_RESPONSE = "analyze_response"
103
+ EXTRACT_DATA = "extract_data"
104
+ ESCALATE = "escalate"
105
+ CONCLUDE = "conclude"
106
+
107
+
108
+ @dataclass
109
+ class ActionResult:
110
+ """Result of an exploitation action."""
111
+ action: ExploitAction
112
+ success: bool
113
+ response_code: Optional[int] = None
114
+ response_body: Optional[str] = None
115
+ response_time_ms: Optional[int] = None
116
+ extracted_data: Optional[dict] = None
117
+ error: Optional[str] = None
118
+ timestamp: datetime = field(default_factory=datetime.utcnow)
119
+
120
+ def to_dict(self) -> dict[str, Any]:
121
+ return {
122
+ "action": self.action.value,
123
+ "success": self.success,
124
+ "response_code": self.response_code,
125
+ "response_body": self.response_body[:500] if self.response_body else None,
126
+ "response_time_ms": self.response_time_ms,
127
+ "extracted_data": self.extracted_data,
128
+ "error": self.error,
129
+ }
130
+
131
+
132
+ @dataclass
133
+ class ExploitStep:
134
+ """A single step in the exploitation process."""
135
+ step_number: int
136
+ reasoning: str
137
+ action: ExploitAction
138
+ parameters: dict[str, Any]
139
+ expected_outcome: str
140
+ result: Optional[ActionResult] = None
141
+
142
+ def to_dict(self) -> dict[str, Any]:
143
+ return {
144
+ "step": self.step_number,
145
+ "reasoning": self.reasoning,
146
+ "action": self.action.value,
147
+ "parameters": self.parameters,
148
+ "expected_outcome": self.expected_outcome,
149
+ "result": self.result.to_dict() if self.result else None,
150
+ }
151
+
152
+
153
+ @dataclass
154
+ class ExploitResult:
155
+ """Final result of exploitation attempt."""
156
+ success: bool
157
+ finding: Finding
158
+ steps: list[ExploitStep]
159
+ evidence: str
160
+ impact_achieved: str
161
+ exploitation_time_seconds: float
162
+ total_requests: int
163
+
164
+ def to_dict(self) -> dict[str, Any]:
165
+ return {
166
+ "success": self.success,
167
+ "finding_title": self.finding.title,
168
+ "finding_url": self.finding.url,
169
+ "steps": [s.to_dict() for s in self.steps],
170
+ "evidence": self.evidence,
171
+ "impact_achieved": self.impact_achieved,
172
+ "exploitation_time_seconds": self.exploitation_time_seconds,
173
+ "total_requests": self.total_requests,
174
+ }
175
+
176
+
177
+ class ExploitReasoningAgent:
178
+ """
179
+ LLM-powered agent that reasons through exploitation.
180
+
181
+ Uses a Think-Act-Observe loop to intelligently exploit vulnerabilities:
182
+ 1. THINK: Analyze current state and plan next action
183
+ 2. ACT: Execute the planned action
184
+ 3. OBSERVE: Analyze the result and update state
185
+ 4. REPEAT until success or max attempts
186
+
187
+ Example:
188
+ agent = ExploitReasoningAgent()
189
+
190
+ result = await agent.exploit(
191
+ finding=sqli_finding,
192
+ objective="Extract database credentials",
193
+ max_attempts=15
194
+ )
195
+
196
+ if result.success:
197
+ print(f"Exploitation successful!")
198
+ print(f"Evidence: {result.evidence}")
199
+ """
200
+
201
+ def __init__(
202
+ self,
203
+ llm_provider: str = "anthropic",
204
+ llm_model: str = "claude-3-5-sonnet-20241022",
205
+ http_client: Any = None,
206
+ ):
207
+ self.llm_provider = llm_provider
208
+ self.llm_model = llm_model
209
+ self.http_client = http_client
210
+ self._llm = None
211
+ self._action_handlers: dict[ExploitAction, Callable] = {}
212
+ self._setup_default_handlers()
213
+
214
+ async def _get_llm(self):
215
+ """Get or create LLM client."""
216
+ if self._llm is None:
217
+ try:
218
+ import litellm
219
+ self._llm = litellm
220
+ except ImportError:
221
+ logger.error("litellm not installed")
222
+ return None
223
+ return self._llm
224
+
225
+ def _setup_default_handlers(self):
226
+ """Set up default action handlers."""
227
+ self._action_handlers = {
228
+ ExploitAction.SEND_REQUEST: self._handle_send_request,
229
+ ExploitAction.TEST_PAYLOAD: self._handle_test_payload,
230
+ ExploitAction.ANALYZE_RESPONSE: self._handle_analyze_response,
231
+ ExploitAction.EXTRACT_DATA: self._handle_extract_data,
232
+ ExploitAction.ESCALATE: self._handle_escalate,
233
+ ExploitAction.CONCLUDE: self._handle_conclude,
234
+ }
235
+
236
+ def register_handler(self, action: ExploitAction, handler: Callable):
237
+ """Register a custom handler for an action."""
238
+ self._action_handlers[action] = handler
239
+
240
+ async def exploit(
241
+ self,
242
+ finding: Finding,
243
+ objective: str = "Achieve successful exploitation",
244
+ tech_stack: str = None,
245
+ waf: str = None,
246
+ auth_status: str = "None",
247
+ max_attempts: int = 15,
248
+ ) -> ExploitResult:
249
+ """
250
+ Attempt to exploit a vulnerability using reasoning.
251
+
252
+ Args:
253
+ finding: The vulnerability finding to exploit
254
+ objective: The exploitation objective
255
+ tech_stack: Target technology stack
256
+ waf: Detected WAF name
257
+ auth_status: Current authentication status
258
+ max_attempts: Maximum exploitation attempts
259
+
260
+ Returns:
261
+ ExploitResult with exploitation outcome
262
+ """
263
+ start_time = datetime.utcnow()
264
+ steps: list[ExploitStep] = []
265
+ total_requests = 0
266
+ last_response = None
267
+
268
+ llm = await self._get_llm()
269
+ if llm is None:
270
+ return self._failed_result(finding, "LLM not available", steps, 0, 0)
271
+
272
+ # Build exploitation context
273
+ context = {
274
+ "title": finding.title,
275
+ "vuln_type": finding.vuln_type.value,
276
+ "severity": finding.severity.value,
277
+ "url": finding.url,
278
+ "parameter": finding.parameter or "N/A",
279
+ "evidence": finding.evidence[:500] if finding.evidence else "None",
280
+ "tech_stack": tech_stack or "Unknown",
281
+ "waf": waf or "None detected",
282
+ "auth_status": auth_status,
283
+ "objective": objective,
284
+ }
285
+
286
+ logger.info(f"Starting exploitation of: {finding.title}")
287
+
288
+ for attempt in range(max_attempts):
289
+ # Build history from previous steps
290
+ history = self._format_history(steps)
291
+ context["history"] = history
292
+
293
+ # Get next action from LLM
294
+ try:
295
+ prompt = EXPLOIT_REASONING_PROMPT.format(**context)
296
+ response = await self._call_llm(prompt)
297
+ action_data = self._parse_action(response)
298
+ except Exception as e:
299
+ logger.warning(f"LLM call failed at step {attempt + 1}: {e}")
300
+ continue
301
+
302
+ # Create step
303
+ try:
304
+ action = ExploitAction(action_data.get("action", "conclude"))
305
+ except ValueError:
306
+ action = ExploitAction.CONCLUDE
307
+
308
+ step = ExploitStep(
309
+ step_number=attempt + 1,
310
+ reasoning=action_data.get("reasoning", ""),
311
+ action=action,
312
+ parameters=action_data.get("parameters", {}),
313
+ expected_outcome=action_data.get("expected_outcome", ""),
314
+ )
315
+
316
+ # Execute action
317
+ handler = self._action_handlers.get(action)
318
+ if handler:
319
+ try:
320
+ result = await handler(
321
+ finding=finding,
322
+ parameters=step.parameters,
323
+ last_response=last_response,
324
+ )
325
+ step.result = result
326
+ if result.response_code:
327
+ total_requests += 1
328
+ last_response = result
329
+ except Exception as e:
330
+ step.result = ActionResult(
331
+ action=action,
332
+ success=False,
333
+ error=str(e),
334
+ )
335
+
336
+ steps.append(step)
337
+
338
+ # Check for conclusion
339
+ if action == ExploitAction.CONCLUDE:
340
+ success = step.parameters.get("success", False)
341
+ evidence = step.parameters.get("evidence", "")
342
+ impact = step.parameters.get("impact", "")
343
+
344
+ elapsed = (datetime.utcnow() - start_time).total_seconds()
345
+
346
+ return ExploitResult(
347
+ success=success,
348
+ finding=finding,
349
+ steps=steps,
350
+ evidence=evidence,
351
+ impact_achieved=impact,
352
+ exploitation_time_seconds=elapsed,
353
+ total_requests=total_requests,
354
+ )
355
+
356
+ # Log progress
357
+ logger.debug(f"Step {attempt + 1}: {action.value} - "
358
+ f"{'Success' if step.result and step.result.success else 'Failed'}")
359
+
360
+ # Max attempts reached
361
+ elapsed = (datetime.utcnow() - start_time).total_seconds()
362
+ return ExploitResult(
363
+ success=False,
364
+ finding=finding,
365
+ steps=steps,
366
+ evidence="Max attempts reached without successful exploitation",
367
+ impact_achieved="None - exploitation unsuccessful",
368
+ exploitation_time_seconds=elapsed,
369
+ total_requests=total_requests,
370
+ )
371
+
372
+ async def _call_llm(self, prompt: str) -> str:
373
+ """Call LLM for reasoning."""
374
+ llm = await self._get_llm()
375
+
376
+ model_str = f"{self.llm_provider}/{self.llm_model}"
377
+ if self.llm_provider == "anthropic" and not self.llm_model.startswith("anthropic/"):
378
+ model_str = f"anthropic/{self.llm_model}"
379
+ elif self.llm_provider == "openai" and not self.llm_model.startswith("openai/"):
380
+ model_str = f"openai/{self.llm_model}"
381
+
382
+ response = await llm.acompletion(
383
+ model=model_str,
384
+ messages=[{"role": "user", "content": prompt}],
385
+ max_tokens=1500,
386
+ temperature=0.3,
387
+ )
388
+
389
+ return response.choices[0].message.content
390
+
391
+ def _parse_action(self, response: str) -> dict[str, Any]:
392
+ """Parse action from LLM response."""
393
+ try:
394
+ # Extract JSON from response
395
+ json_start = response.find("{")
396
+ json_end = response.rfind("}") + 1
397
+ if json_start >= 0 and json_end > json_start:
398
+ return json.loads(response[json_start:json_end])
399
+ except (json.JSONDecodeError, ValueError):
400
+ pass
401
+
402
+ # Default to conclude if parsing fails
403
+ return {
404
+ "reasoning": "Failed to parse response",
405
+ "action": "conclude",
406
+ "parameters": {"success": False, "evidence": "Parse error"},
407
+ }
408
+
409
+ def _format_history(self, steps: list[ExploitStep]) -> str:
410
+ """Format exploitation history for LLM context."""
411
+ if not steps:
412
+ return "No previous actions taken."
413
+
414
+ lines = ["Previous exploitation attempts:"]
415
+ for step in steps[-5:]: # Only last 5 steps for context
416
+ result_str = "Success" if step.result and step.result.success else "Failed"
417
+ lines.append(f"- Step {step.step_number}: {step.action.value} - {result_str}")
418
+ if step.result and step.result.error:
419
+ lines.append(f" Error: {step.result.error}")
420
+ if step.result and step.result.response_code:
421
+ lines.append(f" Response: HTTP {step.result.response_code}")
422
+
423
+ return "\n".join(lines)
424
+
425
+ # ==================== Action Handlers ====================
426
+
427
+ async def _handle_send_request(
428
+ self,
429
+ finding: Finding,
430
+ parameters: dict[str, Any],
431
+ last_response: Optional[ActionResult],
432
+ ) -> ActionResult:
433
+ """Handle send_request action."""
434
+ import time
435
+
436
+ method = parameters.get("method", "GET").upper()
437
+ url = parameters.get("url", finding.url)
438
+ headers = parameters.get("headers", {})
439
+ body = parameters.get("body")
440
+ params = parameters.get("params", {})
441
+
442
+ try:
443
+ import httpx
444
+
445
+ start = time.time()
446
+ async with httpx.AsyncClient(verify=False, timeout=30) as client:
447
+ response = await client.request(
448
+ method=method,
449
+ url=url,
450
+ headers=headers,
451
+ content=body,
452
+ params=params,
453
+ )
454
+ elapsed_ms = int((time.time() - start) * 1000)
455
+
456
+ return ActionResult(
457
+ action=ExploitAction.SEND_REQUEST,
458
+ success=response.status_code < 400,
459
+ response_code=response.status_code,
460
+ response_body=response.text[:2000],
461
+ response_time_ms=elapsed_ms,
462
+ )
463
+
464
+ except ImportError:
465
+ return ActionResult(
466
+ action=ExploitAction.SEND_REQUEST,
467
+ success=False,
468
+ error="httpx not installed",
469
+ )
470
+ except Exception as e:
471
+ return ActionResult(
472
+ action=ExploitAction.SEND_REQUEST,
473
+ success=False,
474
+ error=str(e),
475
+ )
476
+
477
+ async def _handle_test_payload(
478
+ self,
479
+ finding: Finding,
480
+ parameters: dict[str, Any],
481
+ last_response: Optional[ActionResult],
482
+ ) -> ActionResult:
483
+ """Handle test_payload action."""
484
+ import time
485
+ import urllib.parse
486
+
487
+ payload = parameters.get("payload", "")
488
+ encoding = parameters.get("encoding", "none")
489
+
490
+ # Apply encoding
491
+ if encoding == "url":
492
+ payload = urllib.parse.quote(payload)
493
+ elif encoding == "double_url":
494
+ payload = urllib.parse.quote(urllib.parse.quote(payload))
495
+ elif encoding == "base64":
496
+ import base64
497
+ payload = base64.b64encode(payload.encode()).decode()
498
+
499
+ # Build request with payload
500
+ url = finding.url
501
+ if finding.parameter:
502
+ if "?" in url:
503
+ url = f"{url}&{finding.parameter}={payload}"
504
+ else:
505
+ url = f"{url}?{finding.parameter}={payload}"
506
+
507
+ try:
508
+ import httpx
509
+
510
+ start = time.time()
511
+ async with httpx.AsyncClient(verify=False, timeout=30) as client:
512
+ response = await client.get(url)
513
+ elapsed_ms = int((time.time() - start) * 1000)
514
+
515
+ # Check for success indicators
516
+ success_indicators = [
517
+ response.status_code == 200,
518
+ "error" in response.text.lower(),
519
+ "sql" in response.text.lower(),
520
+ "syntax" in response.text.lower(),
521
+ ]
522
+
523
+ return ActionResult(
524
+ action=ExploitAction.TEST_PAYLOAD,
525
+ success=any(success_indicators),
526
+ response_code=response.status_code,
527
+ response_body=response.text[:2000],
528
+ response_time_ms=elapsed_ms,
529
+ )
530
+
531
+ except Exception as e:
532
+ return ActionResult(
533
+ action=ExploitAction.TEST_PAYLOAD,
534
+ success=False,
535
+ error=str(e),
536
+ )
537
+
538
+ async def _handle_analyze_response(
539
+ self,
540
+ finding: Finding,
541
+ parameters: dict[str, Any],
542
+ last_response: Optional[ActionResult],
543
+ ) -> ActionResult:
544
+ """Handle analyze_response action."""
545
+ check_for = parameters.get("check_for", "error")
546
+
547
+ if not last_response or not last_response.response_body:
548
+ return ActionResult(
549
+ action=ExploitAction.ANALYZE_RESPONSE,
550
+ success=False,
551
+ error="No previous response to analyze",
552
+ )
553
+
554
+ body = last_response.response_body.lower()
555
+
556
+ # Check for various indicators
557
+ found = False
558
+ extracted = {}
559
+
560
+ if check_for == "error":
561
+ error_keywords = ["error", "exception", "warning", "syntax", "unexpected"]
562
+ found = any(kw in body for kw in error_keywords)
563
+ extracted["errors_found"] = found
564
+
565
+ elif check_for == "success_indicator":
566
+ success_keywords = ["success", "welcome", "logged in", "dashboard"]
567
+ found = any(kw in body for kw in success_keywords)
568
+ extracted["success_found"] = found
569
+
570
+ elif check_for == "data":
571
+ # Look for data patterns
572
+ import re
573
+ emails = re.findall(r'[\w\.-]+@[\w\.-]+', last_response.response_body)
574
+ extracted["emails"] = emails[:10]
575
+ found = len(emails) > 0
576
+
577
+ return ActionResult(
578
+ action=ExploitAction.ANALYZE_RESPONSE,
579
+ success=found,
580
+ extracted_data=extracted,
581
+ )
582
+
583
+ async def _handle_extract_data(
584
+ self,
585
+ finding: Finding,
586
+ parameters: dict[str, Any],
587
+ last_response: Optional[ActionResult],
588
+ ) -> ActionResult:
589
+ """Handle extract_data action."""
590
+ import re
591
+
592
+ pattern = parameters.get("pattern", "")
593
+ data_type = parameters.get("data_type", "generic")
594
+
595
+ if not last_response or not last_response.response_body:
596
+ return ActionResult(
597
+ action=ExploitAction.EXTRACT_DATA,
598
+ success=False,
599
+ error="No response to extract from",
600
+ )
601
+
602
+ body = last_response.response_body
603
+ extracted = {}
604
+
605
+ try:
606
+ if pattern:
607
+ matches = re.findall(pattern, body)
608
+ extracted["matches"] = matches[:20]
609
+ else:
610
+ # Use data_type specific patterns
611
+ if data_type == "credentials":
612
+ extracted["usernames"] = re.findall(r'username["\']?\s*[:=]\s*["\']?(\w+)', body, re.I)
613
+ extracted["passwords"] = re.findall(r'password["\']?\s*[:=]\s*["\']?([^\s"\']+)', body, re.I)
614
+ elif data_type == "tokens":
615
+ extracted["tokens"] = re.findall(r'token["\']?\s*[:=]\s*["\']?([a-zA-Z0-9_-]{20,})', body, re.I)
616
+ elif data_type == "database":
617
+ extracted["tables"] = re.findall(r'table[:\s]+([a-zA-Z_]+)', body, re.I)
618
+ elif data_type == "files":
619
+ extracted["paths"] = re.findall(r'(/[a-zA-Z0-9_/.-]+)', body)
620
+
621
+ success = any(v for v in extracted.values() if v)
622
+
623
+ return ActionResult(
624
+ action=ExploitAction.EXTRACT_DATA,
625
+ success=success,
626
+ extracted_data=extracted,
627
+ )
628
+
629
+ except re.error as e:
630
+ return ActionResult(
631
+ action=ExploitAction.EXTRACT_DATA,
632
+ success=False,
633
+ error=f"Regex error: {e}",
634
+ )
635
+
636
+ async def _handle_escalate(
637
+ self,
638
+ finding: Finding,
639
+ parameters: dict[str, Any],
640
+ last_response: Optional[ActionResult],
641
+ ) -> ActionResult:
642
+ """Handle escalation action."""
643
+ technique = parameters.get("technique", "")
644
+
645
+ # Escalation is context-dependent and may require custom implementation
646
+ return ActionResult(
647
+ action=ExploitAction.ESCALATE,
648
+ success=False,
649
+ error=f"Escalation technique '{technique}' requires custom implementation",
650
+ )
651
+
652
+ async def _handle_conclude(
653
+ self,
654
+ finding: Finding,
655
+ parameters: dict[str, Any],
656
+ last_response: Optional[ActionResult],
657
+ ) -> ActionResult:
658
+ """Handle conclusion action."""
659
+ success = parameters.get("success", False)
660
+ evidence = parameters.get("evidence", "")
661
+
662
+ return ActionResult(
663
+ action=ExploitAction.CONCLUDE,
664
+ success=success,
665
+ extracted_data={
666
+ "conclusion": "success" if success else "failure",
667
+ "evidence": evidence,
668
+ },
669
+ )
670
+
671
+ def _failed_result(
672
+ self,
673
+ finding: Finding,
674
+ reason: str,
675
+ steps: list[ExploitStep],
676
+ elapsed: float,
677
+ requests: int,
678
+ ) -> ExploitResult:
679
+ """Create a failed result."""
680
+ return ExploitResult(
681
+ success=False,
682
+ finding=finding,
683
+ steps=steps,
684
+ evidence=reason,
685
+ impact_achieved="None - exploitation failed",
686
+ exploitation_time_seconds=elapsed,
687
+ total_requests=requests,
688
+ )