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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- 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
|
+
)
|