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,518 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT LLM-Powered Tool Selection
|
|
3
|
+
|
|
4
|
+
Uses LLM intelligence to dynamically select the most appropriate security tools
|
|
5
|
+
based on:
|
|
6
|
+
- Current scan phase
|
|
7
|
+
- Findings discovered so far
|
|
8
|
+
- Target characteristics (tech stack, WAF, etc.)
|
|
9
|
+
- Time/resource constraints
|
|
10
|
+
|
|
11
|
+
This replaces static tool lists with intelligent, context-aware selection.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
|
|
22
|
+
from aipt_v2.models.findings import Finding, VulnerabilityType
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Available tools by category
|
|
28
|
+
AVAILABLE_TOOLS = {
|
|
29
|
+
"recon": {
|
|
30
|
+
"subfinder": {
|
|
31
|
+
"description": "Fast subdomain discovery using passive sources",
|
|
32
|
+
"best_for": ["subdomain_enumeration", "asset_discovery"],
|
|
33
|
+
"speed": "fast",
|
|
34
|
+
"noise": "low",
|
|
35
|
+
},
|
|
36
|
+
"amass": {
|
|
37
|
+
"description": "Comprehensive subdomain enumeration with active/passive modes",
|
|
38
|
+
"best_for": ["deep_subdomain_enum", "asset_discovery"],
|
|
39
|
+
"speed": "slow",
|
|
40
|
+
"noise": "medium",
|
|
41
|
+
},
|
|
42
|
+
"httpx": {
|
|
43
|
+
"description": "HTTP probing to find live hosts and tech fingerprinting",
|
|
44
|
+
"best_for": ["live_host_detection", "tech_detection"],
|
|
45
|
+
"speed": "fast",
|
|
46
|
+
"noise": "low",
|
|
47
|
+
},
|
|
48
|
+
"nmap": {
|
|
49
|
+
"description": "Port scanning and service detection",
|
|
50
|
+
"best_for": ["port_scan", "service_detection", "os_detection"],
|
|
51
|
+
"speed": "medium",
|
|
52
|
+
"noise": "high",
|
|
53
|
+
},
|
|
54
|
+
"whatweb": {
|
|
55
|
+
"description": "Web technology fingerprinting",
|
|
56
|
+
"best_for": ["tech_detection", "cms_detection"],
|
|
57
|
+
"speed": "fast",
|
|
58
|
+
"noise": "low",
|
|
59
|
+
},
|
|
60
|
+
"wafw00f": {
|
|
61
|
+
"description": "Web application firewall detection",
|
|
62
|
+
"best_for": ["waf_detection", "security_posture"],
|
|
63
|
+
"speed": "fast",
|
|
64
|
+
"noise": "low",
|
|
65
|
+
},
|
|
66
|
+
"waybackurls": {
|
|
67
|
+
"description": "Fetch URLs from Wayback Machine archives",
|
|
68
|
+
"best_for": ["url_discovery", "historical_endpoints"],
|
|
69
|
+
"speed": "fast",
|
|
70
|
+
"noise": "none",
|
|
71
|
+
},
|
|
72
|
+
"gau": {
|
|
73
|
+
"description": "Fetch known URLs from AlienVault, Wayback, Common Crawl",
|
|
74
|
+
"best_for": ["url_discovery", "parameter_discovery"],
|
|
75
|
+
"speed": "fast",
|
|
76
|
+
"noise": "none",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
"scan": {
|
|
80
|
+
"nuclei": {
|
|
81
|
+
"description": "Template-based vulnerability scanner",
|
|
82
|
+
"best_for": ["known_vulns", "cve_detection", "misconfigs"],
|
|
83
|
+
"speed": "fast",
|
|
84
|
+
"noise": "medium",
|
|
85
|
+
},
|
|
86
|
+
"nikto": {
|
|
87
|
+
"description": "Web server scanner for dangerous files/CGIs",
|
|
88
|
+
"best_for": ["web_server_vulns", "misconfigs", "default_files"],
|
|
89
|
+
"speed": "slow",
|
|
90
|
+
"noise": "high",
|
|
91
|
+
},
|
|
92
|
+
"wpscan": {
|
|
93
|
+
"description": "WordPress vulnerability scanner",
|
|
94
|
+
"best_for": ["wordpress_vulns", "plugin_vulns", "theme_vulns"],
|
|
95
|
+
"speed": "medium",
|
|
96
|
+
"noise": "medium",
|
|
97
|
+
"requires": ["wordpress"],
|
|
98
|
+
},
|
|
99
|
+
"sqlmap": {
|
|
100
|
+
"description": "Automatic SQL injection detection and exploitation",
|
|
101
|
+
"best_for": ["sqli_detection", "sqli_exploitation", "db_dump"],
|
|
102
|
+
"speed": "slow",
|
|
103
|
+
"noise": "high",
|
|
104
|
+
},
|
|
105
|
+
"ffuf": {
|
|
106
|
+
"description": "Fast web fuzzer for content discovery",
|
|
107
|
+
"best_for": ["dir_bruteforce", "parameter_fuzzing", "vhost_discovery"],
|
|
108
|
+
"speed": "fast",
|
|
109
|
+
"noise": "high",
|
|
110
|
+
},
|
|
111
|
+
"gobuster": {
|
|
112
|
+
"description": "Directory/file & DNS busting tool",
|
|
113
|
+
"best_for": ["dir_bruteforce", "dns_bruteforce"],
|
|
114
|
+
"speed": "fast",
|
|
115
|
+
"noise": "high",
|
|
116
|
+
},
|
|
117
|
+
"dirsearch": {
|
|
118
|
+
"description": "Web path scanner",
|
|
119
|
+
"best_for": ["dir_bruteforce", "backup_files"],
|
|
120
|
+
"speed": "medium",
|
|
121
|
+
"noise": "high",
|
|
122
|
+
},
|
|
123
|
+
"sslscan": {
|
|
124
|
+
"description": "SSL/TLS configuration scanner",
|
|
125
|
+
"best_for": ["ssl_vulns", "cipher_analysis", "cert_issues"],
|
|
126
|
+
"speed": "fast",
|
|
127
|
+
"noise": "low",
|
|
128
|
+
},
|
|
129
|
+
"testssl": {
|
|
130
|
+
"description": "Comprehensive SSL/TLS testing",
|
|
131
|
+
"best_for": ["ssl_vulns", "protocol_analysis"],
|
|
132
|
+
"speed": "medium",
|
|
133
|
+
"noise": "low",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
"exploit": {
|
|
137
|
+
"sqlmap": {
|
|
138
|
+
"description": "SQL injection exploitation and data extraction",
|
|
139
|
+
"best_for": ["sqli_exploitation", "db_dump", "os_shell"],
|
|
140
|
+
"speed": "slow",
|
|
141
|
+
"noise": "high",
|
|
142
|
+
},
|
|
143
|
+
"commix": {
|
|
144
|
+
"description": "Command injection exploitation",
|
|
145
|
+
"best_for": ["command_injection", "os_shell"],
|
|
146
|
+
"speed": "medium",
|
|
147
|
+
"noise": "high",
|
|
148
|
+
},
|
|
149
|
+
"xsstrike": {
|
|
150
|
+
"description": "Advanced XSS detection and exploitation",
|
|
151
|
+
"best_for": ["xss_detection", "xss_exploitation", "waf_bypass"],
|
|
152
|
+
"speed": "medium",
|
|
153
|
+
"noise": "medium",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
TOOL_SELECTION_PROMPT = """You are an expert penetration tester selecting tools for the next phase of testing.
|
|
160
|
+
|
|
161
|
+
## Current Scan State
|
|
162
|
+
- **Target**: {target}
|
|
163
|
+
- **Phase**: {phase}
|
|
164
|
+
- **Time Budget**: {time_budget} minutes remaining
|
|
165
|
+
- **Stealth Mode**: {stealth_mode}
|
|
166
|
+
|
|
167
|
+
## Findings So Far
|
|
168
|
+
{findings_summary}
|
|
169
|
+
|
|
170
|
+
## Detected Technologies
|
|
171
|
+
{tech_stack}
|
|
172
|
+
|
|
173
|
+
## WAF/Security Controls
|
|
174
|
+
{security_controls}
|
|
175
|
+
|
|
176
|
+
## Tools Already Run
|
|
177
|
+
{tools_run}
|
|
178
|
+
|
|
179
|
+
## Available Tools for {phase} Phase
|
|
180
|
+
{available_tools}
|
|
181
|
+
|
|
182
|
+
## Your Task
|
|
183
|
+
Select the 3-5 most valuable tools to run next. Consider:
|
|
184
|
+
|
|
185
|
+
1. **Gap Analysis**: What information are we missing?
|
|
186
|
+
2. **Finding Follow-up**: Which findings need deeper investigation?
|
|
187
|
+
3. **Attack Surface**: What haven't we explored yet?
|
|
188
|
+
4. **Efficiency**: Prioritize fast tools if time-limited
|
|
189
|
+
5. **Stealth**: Avoid noisy tools if stealth_mode is True
|
|
190
|
+
6. **Tech-Specific**: Use specialized tools for detected technologies
|
|
191
|
+
|
|
192
|
+
## Output Format (JSON)
|
|
193
|
+
```json
|
|
194
|
+
{{
|
|
195
|
+
"selected_tools": [
|
|
196
|
+
{{
|
|
197
|
+
"name": "tool_name",
|
|
198
|
+
"priority": 1,
|
|
199
|
+
"reasoning": "Why this tool now",
|
|
200
|
+
"expected_findings": "What we hope to discover",
|
|
201
|
+
"custom_args": "Any specific arguments or targets"
|
|
202
|
+
}}
|
|
203
|
+
],
|
|
204
|
+
"skip_tools": [
|
|
205
|
+
{{
|
|
206
|
+
"name": "tool_name",
|
|
207
|
+
"reason": "Why skip this tool"
|
|
208
|
+
}}
|
|
209
|
+
],
|
|
210
|
+
"overall_strategy": "Brief description of testing strategy"
|
|
211
|
+
}}
|
|
212
|
+
```"""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
class ToolSelection:
|
|
217
|
+
"""Result of LLM tool selection."""
|
|
218
|
+
name: str
|
|
219
|
+
priority: int
|
|
220
|
+
reasoning: str
|
|
221
|
+
expected_findings: str
|
|
222
|
+
custom_args: Optional[str] = None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class ToolSelectionResult:
|
|
227
|
+
"""Complete tool selection result."""
|
|
228
|
+
selected_tools: list[ToolSelection]
|
|
229
|
+
skip_tools: list[dict[str, str]]
|
|
230
|
+
overall_strategy: str
|
|
231
|
+
confidence: float = 0.9
|
|
232
|
+
selected_at: datetime = field(default_factory=datetime.utcnow)
|
|
233
|
+
|
|
234
|
+
def get_tool_names(self) -> list[str]:
|
|
235
|
+
"""Get just the tool names in priority order."""
|
|
236
|
+
return [t.name for t in sorted(self.selected_tools, key=lambda x: x.priority)]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class LLMToolSelector:
|
|
240
|
+
"""
|
|
241
|
+
LLM-powered intelligent tool selection.
|
|
242
|
+
|
|
243
|
+
Uses an LLM to analyze the current scan state and select the most
|
|
244
|
+
appropriate tools for the next phase of testing.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
selector = LLMToolSelector()
|
|
248
|
+
result = await selector.select_tools(
|
|
249
|
+
target="https://example.com",
|
|
250
|
+
phase="scan",
|
|
251
|
+
findings=[...],
|
|
252
|
+
tech_stack=["WordPress", "PHP", "MySQL"]
|
|
253
|
+
)
|
|
254
|
+
for tool in result.get_tool_names():
|
|
255
|
+
print(f"Run: {tool}")
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
def __init__(
|
|
259
|
+
self,
|
|
260
|
+
llm_provider: str = "anthropic",
|
|
261
|
+
llm_model: str = "claude-3-haiku-20240307",
|
|
262
|
+
):
|
|
263
|
+
self.llm_provider = llm_provider
|
|
264
|
+
self.llm_model = llm_model
|
|
265
|
+
self._llm = None
|
|
266
|
+
|
|
267
|
+
async def _get_llm(self):
|
|
268
|
+
"""Get or create LLM client."""
|
|
269
|
+
if self._llm is None:
|
|
270
|
+
try:
|
|
271
|
+
import litellm
|
|
272
|
+
self._llm = litellm
|
|
273
|
+
except ImportError:
|
|
274
|
+
logger.warning("litellm not installed, falling back to heuristics")
|
|
275
|
+
return None
|
|
276
|
+
return self._llm
|
|
277
|
+
|
|
278
|
+
async def select_tools(
|
|
279
|
+
self,
|
|
280
|
+
target: str,
|
|
281
|
+
phase: str,
|
|
282
|
+
findings: list[Finding],
|
|
283
|
+
tech_stack: list[str] = None,
|
|
284
|
+
waf_detected: str = None,
|
|
285
|
+
tools_already_run: list[str] = None,
|
|
286
|
+
time_budget_minutes: int = 60,
|
|
287
|
+
stealth_mode: bool = False,
|
|
288
|
+
) -> ToolSelectionResult:
|
|
289
|
+
"""
|
|
290
|
+
Select the best tools for the current phase.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
target: Target URL or domain
|
|
294
|
+
phase: Current phase (recon, scan, exploit)
|
|
295
|
+
findings: Findings discovered so far
|
|
296
|
+
tech_stack: Detected technologies
|
|
297
|
+
waf_detected: Detected WAF name
|
|
298
|
+
tools_already_run: Tools that have already been executed
|
|
299
|
+
time_budget_minutes: Remaining time budget
|
|
300
|
+
stealth_mode: Whether to prioritize stealth over thoroughness
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
ToolSelectionResult with prioritized tool list
|
|
304
|
+
"""
|
|
305
|
+
llm = await self._get_llm()
|
|
306
|
+
|
|
307
|
+
if llm is None or not self._has_api_key():
|
|
308
|
+
# Fall back to heuristic selection
|
|
309
|
+
return self._heuristic_selection(
|
|
310
|
+
target, phase, findings, tech_stack,
|
|
311
|
+
waf_detected, tools_already_run, stealth_mode
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Prepare context for LLM
|
|
315
|
+
findings_summary = self._summarize_findings(findings)
|
|
316
|
+
available = AVAILABLE_TOOLS.get(phase, {})
|
|
317
|
+
available_str = json.dumps(available, indent=2)
|
|
318
|
+
|
|
319
|
+
prompt = TOOL_SELECTION_PROMPT.format(
|
|
320
|
+
target=target,
|
|
321
|
+
phase=phase,
|
|
322
|
+
time_budget=time_budget_minutes,
|
|
323
|
+
stealth_mode=stealth_mode,
|
|
324
|
+
findings_summary=findings_summary,
|
|
325
|
+
tech_stack=", ".join(tech_stack) if tech_stack else "Not yet determined",
|
|
326
|
+
security_controls=waf_detected or "None detected",
|
|
327
|
+
tools_run=", ".join(tools_already_run) if tools_already_run else "None yet",
|
|
328
|
+
available_tools=available_str,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
response = await self._call_llm(prompt)
|
|
333
|
+
return self._parse_llm_response(response)
|
|
334
|
+
except Exception as e:
|
|
335
|
+
logger.warning(f"LLM tool selection failed: {e}, falling back to heuristics")
|
|
336
|
+
return self._heuristic_selection(
|
|
337
|
+
target, phase, findings, tech_stack,
|
|
338
|
+
waf_detected, tools_already_run, stealth_mode
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
async def _call_llm(self, prompt: str) -> str:
|
|
342
|
+
"""Call LLM and get response."""
|
|
343
|
+
llm = await self._get_llm()
|
|
344
|
+
|
|
345
|
+
model_str = f"{self.llm_provider}/{self.llm_model}"
|
|
346
|
+
if self.llm_provider == "anthropic" and not self.llm_model.startswith("anthropic/"):
|
|
347
|
+
model_str = f"anthropic/{self.llm_model}"
|
|
348
|
+
elif self.llm_provider == "openai" and not self.llm_model.startswith("openai/"):
|
|
349
|
+
model_str = f"openai/{self.llm_model}"
|
|
350
|
+
|
|
351
|
+
response = await llm.acompletion(
|
|
352
|
+
model=model_str,
|
|
353
|
+
messages=[{"role": "user", "content": prompt}],
|
|
354
|
+
max_tokens=2000,
|
|
355
|
+
temperature=0.3,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return response.choices[0].message.content
|
|
359
|
+
|
|
360
|
+
def _parse_llm_response(self, response: str) -> ToolSelectionResult:
|
|
361
|
+
"""Parse LLM response into ToolSelectionResult."""
|
|
362
|
+
try:
|
|
363
|
+
# Extract JSON from response
|
|
364
|
+
json_start = response.find("{")
|
|
365
|
+
json_end = response.rfind("}") + 1
|
|
366
|
+
if json_start >= 0 and json_end > json_start:
|
|
367
|
+
json_str = response[json_start:json_end]
|
|
368
|
+
data = json.loads(json_str)
|
|
369
|
+
else:
|
|
370
|
+
raise ValueError("No JSON found in response")
|
|
371
|
+
|
|
372
|
+
selected = []
|
|
373
|
+
for tool_data in data.get("selected_tools", []):
|
|
374
|
+
selected.append(ToolSelection(
|
|
375
|
+
name=tool_data["name"],
|
|
376
|
+
priority=tool_data.get("priority", 1),
|
|
377
|
+
reasoning=tool_data.get("reasoning", ""),
|
|
378
|
+
expected_findings=tool_data.get("expected_findings", ""),
|
|
379
|
+
custom_args=tool_data.get("custom_args"),
|
|
380
|
+
))
|
|
381
|
+
|
|
382
|
+
return ToolSelectionResult(
|
|
383
|
+
selected_tools=selected,
|
|
384
|
+
skip_tools=data.get("skip_tools", []),
|
|
385
|
+
overall_strategy=data.get("overall_strategy", ""),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
|
389
|
+
logger.warning(f"Failed to parse LLM response: {e}")
|
|
390
|
+
# Return empty result, will fall back to heuristics
|
|
391
|
+
raise
|
|
392
|
+
|
|
393
|
+
def _heuristic_selection(
|
|
394
|
+
self,
|
|
395
|
+
target: str,
|
|
396
|
+
phase: str,
|
|
397
|
+
findings: list[Finding],
|
|
398
|
+
tech_stack: list[str] = None,
|
|
399
|
+
waf_detected: str = None,
|
|
400
|
+
tools_already_run: list[str] = None,
|
|
401
|
+
stealth_mode: bool = False,
|
|
402
|
+
) -> ToolSelectionResult:
|
|
403
|
+
"""
|
|
404
|
+
Heuristic-based tool selection when LLM is unavailable.
|
|
405
|
+
|
|
406
|
+
Uses predefined rules based on phase and context.
|
|
407
|
+
"""
|
|
408
|
+
tools_already_run = tools_already_run or []
|
|
409
|
+
tech_stack = tech_stack or []
|
|
410
|
+
selected = []
|
|
411
|
+
|
|
412
|
+
available = AVAILABLE_TOOLS.get(phase, {})
|
|
413
|
+
|
|
414
|
+
if phase == "recon":
|
|
415
|
+
# Standard recon order
|
|
416
|
+
priority_order = ["subfinder", "httpx", "whatweb", "wafw00f", "waybackurls"]
|
|
417
|
+
if not stealth_mode:
|
|
418
|
+
priority_order.extend(["nmap", "amass"])
|
|
419
|
+
|
|
420
|
+
elif phase == "scan":
|
|
421
|
+
priority_order = ["nuclei", "ffuf"]
|
|
422
|
+
|
|
423
|
+
# Add tech-specific tools
|
|
424
|
+
if any("wordpress" in t.lower() for t in tech_stack):
|
|
425
|
+
priority_order.insert(0, "wpscan")
|
|
426
|
+
|
|
427
|
+
# Add SQLi tools if we found potential injection points
|
|
428
|
+
sqli_findings = [f for f in findings if f.vuln_type == VulnerabilityType.SQL_INJECTION]
|
|
429
|
+
if sqli_findings:
|
|
430
|
+
priority_order.insert(0, "sqlmap")
|
|
431
|
+
|
|
432
|
+
if not stealth_mode:
|
|
433
|
+
priority_order.extend(["nikto", "gobuster", "sslscan"])
|
|
434
|
+
|
|
435
|
+
elif phase == "exploit":
|
|
436
|
+
priority_order = []
|
|
437
|
+
|
|
438
|
+
# Select exploit tools based on findings
|
|
439
|
+
for finding in findings:
|
|
440
|
+
if finding.vuln_type == VulnerabilityType.SQL_INJECTION:
|
|
441
|
+
if "sqlmap" not in priority_order:
|
|
442
|
+
priority_order.append("sqlmap")
|
|
443
|
+
elif finding.vuln_type == VulnerabilityType.COMMAND_INJECTION:
|
|
444
|
+
if "commix" not in priority_order:
|
|
445
|
+
priority_order.append("commix")
|
|
446
|
+
elif finding.vuln_type in [VulnerabilityType.XSS_REFLECTED, VulnerabilityType.XSS_STORED]:
|
|
447
|
+
if "xsstrike" not in priority_order:
|
|
448
|
+
priority_order.append("xsstrike")
|
|
449
|
+
|
|
450
|
+
else:
|
|
451
|
+
priority_order = list(available.keys())[:5]
|
|
452
|
+
|
|
453
|
+
# Build selection result
|
|
454
|
+
priority = 1
|
|
455
|
+
for tool_name in priority_order:
|
|
456
|
+
if tool_name in tools_already_run:
|
|
457
|
+
continue
|
|
458
|
+
if tool_name not in available:
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
tool_info = available[tool_name]
|
|
462
|
+
selected.append(ToolSelection(
|
|
463
|
+
name=tool_name,
|
|
464
|
+
priority=priority,
|
|
465
|
+
reasoning=f"Standard {phase} phase tool: {tool_info['description']}",
|
|
466
|
+
expected_findings=f"Discover {', '.join(tool_info['best_for'][:2])}",
|
|
467
|
+
))
|
|
468
|
+
priority += 1
|
|
469
|
+
|
|
470
|
+
if len(selected) >= 5:
|
|
471
|
+
break
|
|
472
|
+
|
|
473
|
+
return ToolSelectionResult(
|
|
474
|
+
selected_tools=selected,
|
|
475
|
+
skip_tools=[],
|
|
476
|
+
overall_strategy=f"Heuristic {phase} phase tool selection",
|
|
477
|
+
confidence=0.7,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def _summarize_findings(self, findings: list[Finding]) -> str:
|
|
481
|
+
"""Summarize findings for LLM context."""
|
|
482
|
+
if not findings:
|
|
483
|
+
return "No findings discovered yet."
|
|
484
|
+
|
|
485
|
+
# Count by severity
|
|
486
|
+
by_severity = {}
|
|
487
|
+
for f in findings:
|
|
488
|
+
sev = f.severity.value
|
|
489
|
+
by_severity[sev] = by_severity.get(sev, 0) + 1
|
|
490
|
+
|
|
491
|
+
# Count by type
|
|
492
|
+
by_type = {}
|
|
493
|
+
for f in findings:
|
|
494
|
+
vtype = f.vuln_type.value
|
|
495
|
+
by_type[vtype] = by_type.get(vtype, 0) + 1
|
|
496
|
+
|
|
497
|
+
lines = [
|
|
498
|
+
f"Total findings: {len(findings)}",
|
|
499
|
+
f"By severity: {by_severity}",
|
|
500
|
+
f"By type: {by_type}",
|
|
501
|
+
"",
|
|
502
|
+
"Notable findings:",
|
|
503
|
+
]
|
|
504
|
+
|
|
505
|
+
# Add top 5 most severe
|
|
506
|
+
sorted_findings = sorted(findings, key=lambda f: f.severity, reverse=True)
|
|
507
|
+
for f in sorted_findings[:5]:
|
|
508
|
+
lines.append(f"- [{f.severity.value}] {f.vuln_type.value}: {f.title} at {f.url}")
|
|
509
|
+
|
|
510
|
+
return "\n".join(lines)
|
|
511
|
+
|
|
512
|
+
def _has_api_key(self) -> bool:
|
|
513
|
+
"""Check if API key is available."""
|
|
514
|
+
if self.llm_provider == "anthropic":
|
|
515
|
+
return bool(os.getenv("ANTHROPIC_API_KEY"))
|
|
516
|
+
if self.llm_provider == "openai":
|
|
517
|
+
return bool(os.getenv("OPENAI_API_KEY"))
|
|
518
|
+
return False
|