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,612 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OWASP ZAP (Zed Attack Proxy) Integration for AIPTX
|
|
3
|
+
|
|
4
|
+
Provides web application security testing through ZAP's REST API.
|
|
5
|
+
Supports spidering, active scanning, and passive scanning.
|
|
6
|
+
|
|
7
|
+
Environment Variables:
|
|
8
|
+
ZAP_URL: ZAP API URL (default: http://localhost:8080)
|
|
9
|
+
ZAP_API_KEY: API key for authentication
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from aipt_v2.tools.scanners.zap_tool import get_zap, zap_scan
|
|
13
|
+
|
|
14
|
+
zap = get_zap()
|
|
15
|
+
if zap.connect():
|
|
16
|
+
scan_id = zap.active_scan("https://example.com")
|
|
17
|
+
result = zap.wait_for_scan(scan_id)
|
|
18
|
+
alerts = zap.get_alerts()
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import time
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from enum import Enum
|
|
28
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
import requests
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ==================== Enums ====================
|
|
36
|
+
|
|
37
|
+
class ScanStatus(Enum):
|
|
38
|
+
"""ZAP scan status."""
|
|
39
|
+
RUNNING = "running"
|
|
40
|
+
COMPLETED = "completed"
|
|
41
|
+
STOPPED = "stopped"
|
|
42
|
+
FAILED = "failed"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RiskLevel(Enum):
|
|
46
|
+
"""ZAP alert risk levels."""
|
|
47
|
+
HIGH = 3
|
|
48
|
+
MEDIUM = 2
|
|
49
|
+
LOW = 1
|
|
50
|
+
INFORMATIONAL = 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ConfidenceLevel(Enum):
|
|
54
|
+
"""ZAP alert confidence levels."""
|
|
55
|
+
HIGH = 3
|
|
56
|
+
MEDIUM = 2
|
|
57
|
+
LOW = 1
|
|
58
|
+
FALSE_POSITIVE = 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ==================== Data Classes ====================
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class ZAPConfig:
|
|
65
|
+
"""Configuration for ZAP connection."""
|
|
66
|
+
base_url: str = field(default_factory=lambda: os.getenv("ZAP_URL", "http://localhost:8080"))
|
|
67
|
+
api_key: str = field(default_factory=lambda: os.getenv("ZAP_API_KEY", ""))
|
|
68
|
+
timeout: int = 30
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ScanResult:
|
|
73
|
+
"""Result of a ZAP scan."""
|
|
74
|
+
scan_id: str
|
|
75
|
+
status: str
|
|
76
|
+
progress: int = 0
|
|
77
|
+
alerts_count: int = 0
|
|
78
|
+
urls_found: int = 0
|
|
79
|
+
start_time: str = ""
|
|
80
|
+
end_time: str = ""
|
|
81
|
+
error: str = ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class Alert:
|
|
86
|
+
"""ZAP security alert."""
|
|
87
|
+
alert_id: str
|
|
88
|
+
name: str
|
|
89
|
+
risk: int
|
|
90
|
+
risk_name: str
|
|
91
|
+
confidence: int
|
|
92
|
+
confidence_name: str
|
|
93
|
+
url: str
|
|
94
|
+
description: str = ""
|
|
95
|
+
solution: str = ""
|
|
96
|
+
reference: str = ""
|
|
97
|
+
cwe_id: int = 0
|
|
98
|
+
wasc_id: int = 0
|
|
99
|
+
evidence: str = ""
|
|
100
|
+
param: str = ""
|
|
101
|
+
attack: str = ""
|
|
102
|
+
other_info: str = ""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ==================== Main Tool Class ====================
|
|
106
|
+
|
|
107
|
+
class ZAPTool:
|
|
108
|
+
"""
|
|
109
|
+
OWASP ZAP integration for AIPTX.
|
|
110
|
+
|
|
111
|
+
Provides methods for:
|
|
112
|
+
- URL spidering
|
|
113
|
+
- Active vulnerability scanning
|
|
114
|
+
- Passive scanning
|
|
115
|
+
- Alert retrieval and management
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, config: Optional[ZAPConfig] = None):
|
|
119
|
+
"""Initialize ZAP tool with configuration."""
|
|
120
|
+
self.config = config or ZAPConfig()
|
|
121
|
+
self._session = requests.Session()
|
|
122
|
+
self._connected = False
|
|
123
|
+
|
|
124
|
+
def _request(self, component: str, action: str, params: dict = None) -> dict:
|
|
125
|
+
"""Make request to ZAP API."""
|
|
126
|
+
url = f"{self.config.base_url}/JSON/{component}/view/{action}/"
|
|
127
|
+
params = params or {}
|
|
128
|
+
if self.config.api_key:
|
|
129
|
+
params["apikey"] = self.config.api_key
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
response = self._session.get(url, params=params, timeout=self.config.timeout)
|
|
133
|
+
response.raise_for_status()
|
|
134
|
+
return response.json()
|
|
135
|
+
except requests.exceptions.RequestException as e:
|
|
136
|
+
logger.error(f"ZAP API error: {e}")
|
|
137
|
+
raise
|
|
138
|
+
|
|
139
|
+
def _action(self, component: str, action: str, params: dict = None) -> dict:
|
|
140
|
+
"""Execute ZAP API action."""
|
|
141
|
+
url = f"{self.config.base_url}/JSON/{component}/action/{action}/"
|
|
142
|
+
params = params or {}
|
|
143
|
+
if self.config.api_key:
|
|
144
|
+
params["apikey"] = self.config.api_key
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
response = self._session.get(url, params=params, timeout=self.config.timeout)
|
|
148
|
+
response.raise_for_status()
|
|
149
|
+
return response.json()
|
|
150
|
+
except requests.exceptions.RequestException as e:
|
|
151
|
+
logger.error(f"ZAP API action error: {e}")
|
|
152
|
+
raise
|
|
153
|
+
|
|
154
|
+
# ==================== Connection ====================
|
|
155
|
+
|
|
156
|
+
def connect(self) -> bool:
|
|
157
|
+
"""Test connection to ZAP."""
|
|
158
|
+
try:
|
|
159
|
+
result = self._request("core", "version")
|
|
160
|
+
self._connected = "version" in result
|
|
161
|
+
if self._connected:
|
|
162
|
+
logger.info(f"Connected to ZAP at {self.config.base_url}")
|
|
163
|
+
return self._connected
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(f"Failed to connect to ZAP: {e}")
|
|
166
|
+
self._connected = False
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
def is_connected(self) -> bool:
|
|
170
|
+
"""Check if connected to ZAP."""
|
|
171
|
+
return self._connected
|
|
172
|
+
|
|
173
|
+
def get_info(self) -> Dict:
|
|
174
|
+
"""Get ZAP version and info."""
|
|
175
|
+
try:
|
|
176
|
+
version = self._request("core", "version")
|
|
177
|
+
return {"version": version.get("version", "unknown")}
|
|
178
|
+
except Exception:
|
|
179
|
+
return {}
|
|
180
|
+
|
|
181
|
+
# ==================== Context Management ====================
|
|
182
|
+
|
|
183
|
+
def new_session(self, name: str = None) -> bool:
|
|
184
|
+
"""Create a new ZAP session."""
|
|
185
|
+
try:
|
|
186
|
+
params = {}
|
|
187
|
+
if name:
|
|
188
|
+
params["name"] = name
|
|
189
|
+
self._action("core", "newSession", params)
|
|
190
|
+
return True
|
|
191
|
+
except Exception:
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
def set_mode(self, mode: str = "standard") -> bool:
|
|
195
|
+
"""Set ZAP mode (safe, protect, standard, attack)."""
|
|
196
|
+
try:
|
|
197
|
+
self._action("core", "setMode", {"mode": mode})
|
|
198
|
+
return True
|
|
199
|
+
except Exception:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
# ==================== Spider ====================
|
|
203
|
+
|
|
204
|
+
def spider(self, url: str, max_children: int = 0, recurse: bool = True) -> str:
|
|
205
|
+
"""
|
|
206
|
+
Start spidering a URL.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
url: Target URL to spider
|
|
210
|
+
max_children: Max children to crawl (0 = unlimited)
|
|
211
|
+
recurse: Whether to recurse into found URLs
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Spider scan ID
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
params = {
|
|
218
|
+
"url": url,
|
|
219
|
+
"maxChildren": str(max_children),
|
|
220
|
+
"recurse": str(recurse).lower()
|
|
221
|
+
}
|
|
222
|
+
result = self._action("spider", "scan", params)
|
|
223
|
+
scan_id = result.get("scan", "")
|
|
224
|
+
logger.info(f"Started spider scan: {scan_id} for {url}")
|
|
225
|
+
return str(scan_id)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"Failed to start spider: {e}")
|
|
228
|
+
return ""
|
|
229
|
+
|
|
230
|
+
def get_spider_status(self, scan_id: str) -> int:
|
|
231
|
+
"""Get spider progress (0-100)."""
|
|
232
|
+
try:
|
|
233
|
+
result = self._request("spider", "status", {"scanId": scan_id})
|
|
234
|
+
return int(result.get("status", 0))
|
|
235
|
+
except Exception:
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
def wait_for_spider(self, scan_id: str, timeout: int = 300) -> bool:
|
|
239
|
+
"""Wait for spider to complete."""
|
|
240
|
+
start_time = time.time()
|
|
241
|
+
while time.time() - start_time < timeout:
|
|
242
|
+
progress = self.get_spider_status(scan_id)
|
|
243
|
+
if progress >= 100:
|
|
244
|
+
return True
|
|
245
|
+
time.sleep(5)
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
def get_spider_results(self, scan_id: str) -> List[str]:
|
|
249
|
+
"""Get URLs found by spider."""
|
|
250
|
+
try:
|
|
251
|
+
result = self._request("spider", "results", {"scanId": scan_id})
|
|
252
|
+
return result.get("results", [])
|
|
253
|
+
except Exception:
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
# ==================== Active Scan ====================
|
|
257
|
+
|
|
258
|
+
def active_scan(
|
|
259
|
+
self,
|
|
260
|
+
url: str,
|
|
261
|
+
recurse: bool = True,
|
|
262
|
+
in_scope_only: bool = False,
|
|
263
|
+
scan_policy: str = None
|
|
264
|
+
) -> str:
|
|
265
|
+
"""
|
|
266
|
+
Start active vulnerability scan.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
url: Target URL to scan
|
|
270
|
+
recurse: Scan recursively
|
|
271
|
+
in_scope_only: Only scan URLs in scope
|
|
272
|
+
scan_policy: Scan policy name (optional)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Active scan ID
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
params = {
|
|
279
|
+
"url": url,
|
|
280
|
+
"recurse": str(recurse).lower(),
|
|
281
|
+
"inScopeOnly": str(in_scope_only).lower()
|
|
282
|
+
}
|
|
283
|
+
if scan_policy:
|
|
284
|
+
params["scanPolicyName"] = scan_policy
|
|
285
|
+
|
|
286
|
+
result = self._action("ascan", "scan", params)
|
|
287
|
+
scan_id = result.get("scan", "")
|
|
288
|
+
logger.info(f"Started active scan: {scan_id} for {url}")
|
|
289
|
+
return str(scan_id)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logger.error(f"Failed to start active scan: {e}")
|
|
292
|
+
return ""
|
|
293
|
+
|
|
294
|
+
def get_scan_status(self, scan_id: str) -> ScanResult:
|
|
295
|
+
"""Get active scan status."""
|
|
296
|
+
try:
|
|
297
|
+
status_result = self._request("ascan", "status", {"scanId": scan_id})
|
|
298
|
+
progress = int(status_result.get("status", 0))
|
|
299
|
+
|
|
300
|
+
# Get alerts count
|
|
301
|
+
alerts = self.get_alerts()
|
|
302
|
+
|
|
303
|
+
status = "running" if progress < 100 else "completed"
|
|
304
|
+
|
|
305
|
+
return ScanResult(
|
|
306
|
+
scan_id=scan_id,
|
|
307
|
+
status=status,
|
|
308
|
+
progress=progress,
|
|
309
|
+
alerts_count=len(alerts)
|
|
310
|
+
)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
return ScanResult(scan_id=scan_id, status="error", error=str(e))
|
|
313
|
+
|
|
314
|
+
def wait_for_scan(
|
|
315
|
+
self,
|
|
316
|
+
scan_id: str,
|
|
317
|
+
timeout: int = 3600,
|
|
318
|
+
poll_interval: int = 10,
|
|
319
|
+
callback: Callable[[ScanResult], None] = None
|
|
320
|
+
) -> ScanResult:
|
|
321
|
+
"""
|
|
322
|
+
Wait for active scan to complete.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
scan_id: Scan ID to monitor
|
|
326
|
+
timeout: Maximum wait time in seconds
|
|
327
|
+
poll_interval: Polling interval in seconds
|
|
328
|
+
callback: Optional progress callback
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Final scan result
|
|
332
|
+
"""
|
|
333
|
+
start_time = time.time()
|
|
334
|
+
while time.time() - start_time < timeout:
|
|
335
|
+
result = self.get_scan_status(scan_id)
|
|
336
|
+
|
|
337
|
+
if callback:
|
|
338
|
+
callback(result)
|
|
339
|
+
|
|
340
|
+
if result.progress >= 100 or result.status in ["completed", "stopped", "error"]:
|
|
341
|
+
result.status = "completed" if result.progress >= 100 else result.status
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
time.sleep(poll_interval)
|
|
345
|
+
|
|
346
|
+
return ScanResult(
|
|
347
|
+
scan_id=scan_id,
|
|
348
|
+
status="timeout",
|
|
349
|
+
error=f"Scan timed out after {timeout}s"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def stop_scan(self, scan_id: str) -> bool:
|
|
353
|
+
"""Stop an active scan."""
|
|
354
|
+
try:
|
|
355
|
+
self._action("ascan", "stop", {"scanId": scan_id})
|
|
356
|
+
return True
|
|
357
|
+
except Exception:
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
def pause_scan(self, scan_id: str) -> bool:
|
|
361
|
+
"""Pause an active scan."""
|
|
362
|
+
try:
|
|
363
|
+
self._action("ascan", "pause", {"scanId": scan_id})
|
|
364
|
+
return True
|
|
365
|
+
except Exception:
|
|
366
|
+
return False
|
|
367
|
+
|
|
368
|
+
def resume_scan(self, scan_id: str) -> bool:
|
|
369
|
+
"""Resume a paused scan."""
|
|
370
|
+
try:
|
|
371
|
+
self._action("ascan", "resume", {"scanId": scan_id})
|
|
372
|
+
return True
|
|
373
|
+
except Exception:
|
|
374
|
+
return False
|
|
375
|
+
|
|
376
|
+
# ==================== Full Scan (Spider + Active) ====================
|
|
377
|
+
|
|
378
|
+
def full_scan(self, url: str, spider_timeout: int = 300) -> str:
|
|
379
|
+
"""
|
|
380
|
+
Run a full scan (spider + active scan).
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
url: Target URL
|
|
384
|
+
spider_timeout: Max time for spider phase
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Active scan ID
|
|
388
|
+
"""
|
|
389
|
+
# First spider the target
|
|
390
|
+
spider_id = self.spider(url)
|
|
391
|
+
if spider_id:
|
|
392
|
+
self.wait_for_spider(spider_id, spider_timeout)
|
|
393
|
+
|
|
394
|
+
# Then run active scan
|
|
395
|
+
return self.active_scan(url)
|
|
396
|
+
|
|
397
|
+
# ==================== Alert Management ====================
|
|
398
|
+
|
|
399
|
+
def get_alerts(
|
|
400
|
+
self,
|
|
401
|
+
base_url: str = None,
|
|
402
|
+
risk: str = None,
|
|
403
|
+
start: int = 0,
|
|
404
|
+
count: int = 1000
|
|
405
|
+
) -> List[Alert]:
|
|
406
|
+
"""
|
|
407
|
+
Get security alerts.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
base_url: Filter by base URL
|
|
411
|
+
risk: Filter by risk level (high, medium, low, informational)
|
|
412
|
+
start: Start index
|
|
413
|
+
count: Number of alerts to retrieve
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
List of Alert objects
|
|
417
|
+
"""
|
|
418
|
+
try:
|
|
419
|
+
params = {"start": str(start), "count": str(count)}
|
|
420
|
+
if base_url:
|
|
421
|
+
params["baseurl"] = base_url
|
|
422
|
+
if risk:
|
|
423
|
+
params["riskId"] = str(self._risk_to_int(risk))
|
|
424
|
+
|
|
425
|
+
result = self._request("alert", "alerts", params)
|
|
426
|
+
alerts = []
|
|
427
|
+
|
|
428
|
+
for a in result.get("alerts", []):
|
|
429
|
+
alerts.append(Alert(
|
|
430
|
+
alert_id=str(a.get("id", "")),
|
|
431
|
+
name=a.get("name", "Unknown"),
|
|
432
|
+
risk=int(a.get("riskcode", 0)),
|
|
433
|
+
risk_name=a.get("risk", "Unknown"),
|
|
434
|
+
confidence=int(a.get("confidence", 0)),
|
|
435
|
+
confidence_name=a.get("confidence", "Unknown"),
|
|
436
|
+
url=a.get("url", ""),
|
|
437
|
+
description=a.get("description", ""),
|
|
438
|
+
solution=a.get("solution", ""),
|
|
439
|
+
reference=a.get("reference", ""),
|
|
440
|
+
cwe_id=int(a.get("cweid", 0) or 0),
|
|
441
|
+
wasc_id=int(a.get("wascid", 0) or 0),
|
|
442
|
+
evidence=a.get("evidence", ""),
|
|
443
|
+
param=a.get("param", ""),
|
|
444
|
+
attack=a.get("attack", ""),
|
|
445
|
+
other_info=a.get("other", "")
|
|
446
|
+
))
|
|
447
|
+
|
|
448
|
+
return alerts
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.error(f"Failed to get alerts: {e}")
|
|
451
|
+
return []
|
|
452
|
+
|
|
453
|
+
def _risk_to_int(self, risk: str) -> int:
|
|
454
|
+
"""Convert risk string to int."""
|
|
455
|
+
mapping = {"high": 3, "medium": 2, "low": 1, "informational": 0, "info": 0}
|
|
456
|
+
return mapping.get(risk.lower(), 0)
|
|
457
|
+
|
|
458
|
+
def get_alerts_summary(self) -> Dict:
|
|
459
|
+
"""Get summary of alerts by risk level."""
|
|
460
|
+
try:
|
|
461
|
+
result = self._request("alert", "alertsSummary")
|
|
462
|
+
return result.get("alertsSummary", {})
|
|
463
|
+
except Exception:
|
|
464
|
+
return {}
|
|
465
|
+
|
|
466
|
+
# ==================== AIPTX Integration ====================
|
|
467
|
+
|
|
468
|
+
def to_finding(self, alert: Alert) -> Dict:
|
|
469
|
+
"""Convert ZAP alert to AIPTX finding format."""
|
|
470
|
+
severity_map = {3: "high", 2: "medium", 1: "low", 0: "info"}
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
"type": "vulnerability",
|
|
474
|
+
"value": alert.name,
|
|
475
|
+
"description": alert.description or alert.name,
|
|
476
|
+
"severity": severity_map.get(alert.risk, "info"),
|
|
477
|
+
"phase": "scan",
|
|
478
|
+
"tool": "zap",
|
|
479
|
+
"target": alert.url,
|
|
480
|
+
"metadata": {
|
|
481
|
+
"alert_id": alert.alert_id,
|
|
482
|
+
"risk": alert.risk_name,
|
|
483
|
+
"confidence": alert.confidence_name,
|
|
484
|
+
"cwe_id": alert.cwe_id,
|
|
485
|
+
"wasc_id": alert.wasc_id,
|
|
486
|
+
"solution": alert.solution,
|
|
487
|
+
"evidence": alert.evidence[:500] if alert.evidence else "",
|
|
488
|
+
"param": alert.param,
|
|
489
|
+
"attack": alert.attack
|
|
490
|
+
},
|
|
491
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
def export_findings(self, output_path: str = None, base_url: str = None) -> List[Dict]:
|
|
495
|
+
"""
|
|
496
|
+
Export alerts in AIPTX format.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
output_path: Optional file path to save JSON
|
|
500
|
+
base_url: Optional URL filter
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
List of AIPTX findings
|
|
504
|
+
"""
|
|
505
|
+
alerts = self.get_alerts(base_url=base_url)
|
|
506
|
+
findings = [self.to_finding(a) for a in alerts]
|
|
507
|
+
|
|
508
|
+
if output_path:
|
|
509
|
+
with open(output_path, "w") as f:
|
|
510
|
+
json.dump(findings, f, indent=2)
|
|
511
|
+
|
|
512
|
+
return findings
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
# ==================== Global Instance & Helper Functions ====================
|
|
516
|
+
|
|
517
|
+
_zap: Optional[ZAPTool] = None
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def get_zap(config: Optional[ZAPConfig] = None) -> ZAPTool:
|
|
521
|
+
"""Get or create global ZAP instance."""
|
|
522
|
+
global _zap
|
|
523
|
+
if _zap is None or config is not None:
|
|
524
|
+
_zap = ZAPTool(config)
|
|
525
|
+
return _zap
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def zap_scan(url: str, full: bool = True) -> ScanResult:
|
|
529
|
+
"""Quick scan a target."""
|
|
530
|
+
zap = get_zap()
|
|
531
|
+
if not zap.connect():
|
|
532
|
+
return ScanResult(scan_id="", status="error", error="Connection failed")
|
|
533
|
+
|
|
534
|
+
if full:
|
|
535
|
+
scan_id = zap.full_scan(url)
|
|
536
|
+
else:
|
|
537
|
+
scan_id = zap.active_scan(url)
|
|
538
|
+
|
|
539
|
+
return zap.get_scan_status(scan_id)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def zap_status(scan_id: str) -> ScanResult:
|
|
543
|
+
"""Get scan status."""
|
|
544
|
+
return get_zap().get_scan_status(scan_id)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def zap_alerts(base_url: str = None, risk: str = None) -> List[Dict]:
|
|
548
|
+
"""Get alerts in AIPTX format."""
|
|
549
|
+
zap = get_zap()
|
|
550
|
+
alerts = zap.get_alerts(base_url=base_url, risk=risk)
|
|
551
|
+
return [zap.to_finding(a) for a in alerts]
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def zap_summary() -> Dict:
|
|
555
|
+
"""Get ZAP scanner summary."""
|
|
556
|
+
zap = get_zap()
|
|
557
|
+
if not zap.connect():
|
|
558
|
+
return {"connected": False, "error": "Connection failed"}
|
|
559
|
+
|
|
560
|
+
info = zap.get_info()
|
|
561
|
+
alerts_summary = zap.get_alerts_summary()
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
"connected": True,
|
|
565
|
+
"url": zap.config.base_url,
|
|
566
|
+
"version": info.get("version", "unknown"),
|
|
567
|
+
"alerts": alerts_summary
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# ==================== CLI Testing ====================
|
|
572
|
+
|
|
573
|
+
if __name__ == "__main__":
|
|
574
|
+
import sys
|
|
575
|
+
|
|
576
|
+
print("OWASP ZAP Integration Test")
|
|
577
|
+
print("=" * 50)
|
|
578
|
+
|
|
579
|
+
zap = get_zap()
|
|
580
|
+
|
|
581
|
+
if zap.connect():
|
|
582
|
+
print(f"✓ Connected to ZAP at {zap.config.base_url}")
|
|
583
|
+
|
|
584
|
+
info = zap.get_info()
|
|
585
|
+
print(f" Version: {info.get('version', 'unknown')}")
|
|
586
|
+
|
|
587
|
+
if len(sys.argv) > 1:
|
|
588
|
+
target = sys.argv[1]
|
|
589
|
+
print(f"\nScanning {target}...")
|
|
590
|
+
|
|
591
|
+
# Spider first
|
|
592
|
+
spider_id = zap.spider(target)
|
|
593
|
+
print(f" Spider ID: {spider_id}")
|
|
594
|
+
zap.wait_for_spider(spider_id)
|
|
595
|
+
urls = zap.get_spider_results(spider_id)
|
|
596
|
+
print(f" URLs found: {len(urls)}")
|
|
597
|
+
|
|
598
|
+
# Active scan
|
|
599
|
+
scan_id = zap.active_scan(target)
|
|
600
|
+
print(f" Scan ID: {scan_id}")
|
|
601
|
+
|
|
602
|
+
result = zap.wait_for_scan(scan_id, timeout=1800)
|
|
603
|
+
print(f" Status: {result.status}")
|
|
604
|
+
|
|
605
|
+
alerts = zap.get_alerts()
|
|
606
|
+
print(f" Alerts: {len(alerts)}")
|
|
607
|
+
|
|
608
|
+
for alert in alerts[:5]:
|
|
609
|
+
print(f" [{alert.risk_name}] {alert.name}")
|
|
610
|
+
else:
|
|
611
|
+
print("✗ Failed to connect to ZAP")
|
|
612
|
+
print(" Set ZAP_URL and ZAP_API_KEY environment variables")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from aipt_v2.tools.registry import register_tool
|
|
6
|
+
|
|
7
|
+
from .terminal_manager import get_terminal_manager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register_tool
|
|
11
|
+
def terminal_execute(
|
|
12
|
+
command: str,
|
|
13
|
+
is_input: bool = False,
|
|
14
|
+
timeout: float | None = None,
|
|
15
|
+
terminal_id: str | None = None,
|
|
16
|
+
no_enter: bool = False,
|
|
17
|
+
) -> dict[str, Any]:
|
|
18
|
+
manager = get_terminal_manager()
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
return manager.execute_command(
|
|
22
|
+
command=command,
|
|
23
|
+
is_input=is_input,
|
|
24
|
+
timeout=timeout,
|
|
25
|
+
terminal_id=terminal_id,
|
|
26
|
+
no_enter=no_enter,
|
|
27
|
+
)
|
|
28
|
+
except (ValueError, RuntimeError) as e:
|
|
29
|
+
return {
|
|
30
|
+
"error": str(e),
|
|
31
|
+
"command": command,
|
|
32
|
+
"terminal_id": terminal_id or "default",
|
|
33
|
+
"content": "",
|
|
34
|
+
"status": "error",
|
|
35
|
+
"exit_code": None,
|
|
36
|
+
"working_dir": None,
|
|
37
|
+
}
|