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,712 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Acunetix Scanner Tool - Plug & Play Integration for AIPT Orchestration
|
|
4
|
+
Provides comprehensive API integration with Acunetix Web Vulnerability Scanner.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import logging
|
|
10
|
+
import requests
|
|
11
|
+
from typing import Optional, Dict, List, Any
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import urllib3
|
|
17
|
+
|
|
18
|
+
# Disable SSL warnings for self-signed certificates
|
|
19
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ScanProfile(Enum):
|
|
25
|
+
"""Acunetix scan profile types."""
|
|
26
|
+
FULL_SCAN = "11111111-1111-1111-1111-111111111111"
|
|
27
|
+
HIGH_RISK = "11111111-1111-1111-1111-111111111112"
|
|
28
|
+
WEAK_PASSWORDS = "11111111-1111-1111-1111-111111111115"
|
|
29
|
+
CRAWL_ONLY = "11111111-1111-1111-1111-111111111117"
|
|
30
|
+
XSS_SCAN = "11111111-1111-1111-1111-111111111116"
|
|
31
|
+
SQL_INJECTION = "11111111-1111-1111-1111-111111111113"
|
|
32
|
+
MALWARE_SCAN = "11111111-1111-1111-1111-111111111120"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ScanStatus(Enum):
|
|
36
|
+
"""Acunetix scan status types."""
|
|
37
|
+
PENDING = "pending"
|
|
38
|
+
QUEUED = "queued"
|
|
39
|
+
STARTING = "starting"
|
|
40
|
+
PROCESSING = "processing"
|
|
41
|
+
COMPLETED = "completed"
|
|
42
|
+
FAILED = "failed"
|
|
43
|
+
ABORTED = "aborted"
|
|
44
|
+
PAUSED = "paused"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Severity(Enum):
|
|
48
|
+
"""Vulnerability severity levels."""
|
|
49
|
+
CRITICAL = "critical"
|
|
50
|
+
HIGH = "high"
|
|
51
|
+
MEDIUM = "medium"
|
|
52
|
+
LOW = "low"
|
|
53
|
+
INFO = "info"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class AcunetixConfig:
|
|
58
|
+
"""Configuration for Acunetix connection."""
|
|
59
|
+
base_url: str = "https://13.127.28.41:3443"
|
|
60
|
+
api_key: str = "1986ad8c0a5b3df4d7028d5f3c06e936c83ef0a486ef74537812989cff1a41a7c"
|
|
61
|
+
verify_ssl: bool = False
|
|
62
|
+
timeout: int = 30
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ScanResult:
|
|
67
|
+
"""Result of an Acunetix scan."""
|
|
68
|
+
scan_id: str
|
|
69
|
+
target_id: str
|
|
70
|
+
target_url: str
|
|
71
|
+
status: str
|
|
72
|
+
progress: int
|
|
73
|
+
start_time: Optional[str] = None
|
|
74
|
+
end_time: Optional[str] = None
|
|
75
|
+
vulnerabilities: Dict[str, int] = field(default_factory=dict)
|
|
76
|
+
threat_level: int = 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class Vulnerability:
|
|
81
|
+
"""Vulnerability finding from Acunetix."""
|
|
82
|
+
vuln_id: str
|
|
83
|
+
severity: str
|
|
84
|
+
name: str
|
|
85
|
+
description: str
|
|
86
|
+
target_url: str
|
|
87
|
+
affected_url: str
|
|
88
|
+
recommendation: Optional[str] = None
|
|
89
|
+
cvss_score: Optional[float] = None
|
|
90
|
+
cwe_id: Optional[str] = None
|
|
91
|
+
references: List[str] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class AcunetixTool:
|
|
95
|
+
"""
|
|
96
|
+
Acunetix Scanner Tool for AIPT Orchestration.
|
|
97
|
+
|
|
98
|
+
Provides plug-and-play integration with Acunetix Web Vulnerability Scanner.
|
|
99
|
+
Supports target management, scan execution, vulnerability retrieval, and reporting.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, config: Optional[AcunetixConfig] = None):
|
|
103
|
+
"""Initialize Acunetix tool with configuration."""
|
|
104
|
+
self.config = config or AcunetixConfig()
|
|
105
|
+
self.session = requests.Session()
|
|
106
|
+
self.session.headers.update({
|
|
107
|
+
"X-Auth": self.config.api_key,
|
|
108
|
+
"Content-Type": "application/json"
|
|
109
|
+
})
|
|
110
|
+
self.session.verify = self.config.verify_ssl
|
|
111
|
+
self._connected = False
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def api_url(self) -> str:
|
|
115
|
+
"""Get the API base URL."""
|
|
116
|
+
return f"{self.config.base_url}/api/v1"
|
|
117
|
+
|
|
118
|
+
def _request(self, method: str, endpoint: str, data: Optional[Dict] = None,
|
|
119
|
+
params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
120
|
+
"""Make an API request to Acunetix."""
|
|
121
|
+
url = f"{self.api_url}/{endpoint}"
|
|
122
|
+
try:
|
|
123
|
+
response = self.session.request(
|
|
124
|
+
method=method,
|
|
125
|
+
url=url,
|
|
126
|
+
json=data,
|
|
127
|
+
params=params,
|
|
128
|
+
timeout=self.config.timeout
|
|
129
|
+
)
|
|
130
|
+
response.raise_for_status()
|
|
131
|
+
if response.content:
|
|
132
|
+
return response.json()
|
|
133
|
+
return {"success": True}
|
|
134
|
+
except requests.exceptions.RequestException as e:
|
|
135
|
+
logger.error(f"Acunetix API error: {e}")
|
|
136
|
+
raise
|
|
137
|
+
|
|
138
|
+
# ==================== Connection ====================
|
|
139
|
+
|
|
140
|
+
def connect(self) -> bool:
|
|
141
|
+
"""Test connection to Acunetix and verify API key."""
|
|
142
|
+
try:
|
|
143
|
+
result = self._request("GET", "me")
|
|
144
|
+
self._connected = result.get("enabled", False)
|
|
145
|
+
logger.info(f"Connected to Acunetix as: {result.get('email')}")
|
|
146
|
+
return self._connected
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Failed to connect to Acunetix: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def get_info(self) -> Dict[str, Any]:
|
|
152
|
+
"""Get Acunetix instance information."""
|
|
153
|
+
return self._request("GET", "me")
|
|
154
|
+
|
|
155
|
+
def is_connected(self) -> bool:
|
|
156
|
+
"""Check if connected to Acunetix."""
|
|
157
|
+
return self._connected
|
|
158
|
+
|
|
159
|
+
# ==================== Target Management ====================
|
|
160
|
+
|
|
161
|
+
def add_target(self, url: str, description: str = "",
|
|
162
|
+
criticality: int = 10) -> str:
|
|
163
|
+
"""
|
|
164
|
+
Add a new target to Acunetix.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
url: Target URL to scan
|
|
168
|
+
description: Target description
|
|
169
|
+
criticality: Target criticality (0-30, 10=normal, 30=critical)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Target ID
|
|
173
|
+
"""
|
|
174
|
+
data = {
|
|
175
|
+
"address": url,
|
|
176
|
+
"description": description or f"AIPT Target - {datetime.now().isoformat()}",
|
|
177
|
+
"criticality": criticality
|
|
178
|
+
}
|
|
179
|
+
result = self._request("POST", "targets", data=data)
|
|
180
|
+
target_id = result.get("target_id")
|
|
181
|
+
logger.info(f"Added target: {url} (ID: {target_id})")
|
|
182
|
+
return target_id
|
|
183
|
+
|
|
184
|
+
def get_target(self, target_id: str) -> Dict[str, Any]:
|
|
185
|
+
"""Get target information by ID."""
|
|
186
|
+
return self._request("GET", f"targets/{target_id}")
|
|
187
|
+
|
|
188
|
+
def get_target_by_url(self, url: str) -> Optional[str]:
|
|
189
|
+
"""Find target ID by URL."""
|
|
190
|
+
targets = self.list_targets()
|
|
191
|
+
for target in targets:
|
|
192
|
+
if target.get("address") == url:
|
|
193
|
+
return target.get("target_id")
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
def list_targets(self, limit: int = 100) -> List[Dict[str, Any]]:
|
|
197
|
+
"""List all targets."""
|
|
198
|
+
result = self._request("GET", "targets", params={"l": limit})
|
|
199
|
+
return result.get("targets", [])
|
|
200
|
+
|
|
201
|
+
def delete_target(self, target_id: str) -> bool:
|
|
202
|
+
"""Delete a target."""
|
|
203
|
+
try:
|
|
204
|
+
self._request("DELETE", f"targets/{target_id}")
|
|
205
|
+
logger.info(f"Deleted target: {target_id}")
|
|
206
|
+
return True
|
|
207
|
+
except Exception:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def configure_target(self, target_id: str, login_url: str = None,
|
|
211
|
+
username: str = None, password: str = None,
|
|
212
|
+
custom_headers: Dict[str, str] = None) -> bool:
|
|
213
|
+
"""
|
|
214
|
+
Configure target with authentication and custom settings.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
target_id: Target ID
|
|
218
|
+
login_url: Login page URL for authenticated scanning
|
|
219
|
+
username: Login username
|
|
220
|
+
password: Login password
|
|
221
|
+
custom_headers: Custom HTTP headers to include
|
|
222
|
+
"""
|
|
223
|
+
config = {}
|
|
224
|
+
|
|
225
|
+
if login_url and username and password:
|
|
226
|
+
config["login"] = {
|
|
227
|
+
"kind": "automatic",
|
|
228
|
+
"credentials": {
|
|
229
|
+
"enabled": True,
|
|
230
|
+
"username": username,
|
|
231
|
+
"password": password
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if custom_headers:
|
|
236
|
+
config["custom_headers"] = [
|
|
237
|
+
{"name": k, "value": v} for k, v in custom_headers.items()
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
if config:
|
|
241
|
+
self._request("PATCH", f"targets/{target_id}/configuration", data=config)
|
|
242
|
+
logger.info(f"Configured target: {target_id}")
|
|
243
|
+
return True
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
# ==================== Scan Management ====================
|
|
247
|
+
|
|
248
|
+
def start_scan(self, target_id: str,
|
|
249
|
+
profile: ScanProfile = ScanProfile.FULL_SCAN,
|
|
250
|
+
schedule: bool = False) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Start a scan on a target.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
target_id: Target ID to scan
|
|
256
|
+
profile: Scan profile to use
|
|
257
|
+
schedule: If True, schedule for later (not immediate)
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Scan ID
|
|
261
|
+
"""
|
|
262
|
+
data = {
|
|
263
|
+
"target_id": target_id,
|
|
264
|
+
"profile_id": profile.value,
|
|
265
|
+
"schedule": {
|
|
266
|
+
"disable": False,
|
|
267
|
+
"start_date": None,
|
|
268
|
+
"time_sensitive": False
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
result = self._request("POST", "scans", data=data)
|
|
272
|
+
scan_id = result.get("scan_id") or self._get_latest_scan_id(target_id)
|
|
273
|
+
logger.info(f"Started scan: {scan_id} with profile: {profile.name}")
|
|
274
|
+
return scan_id
|
|
275
|
+
|
|
276
|
+
def _get_latest_scan_id(self, target_id: str) -> Optional[str]:
|
|
277
|
+
"""Get the latest scan ID for a target."""
|
|
278
|
+
scans = self.list_scans(limit=10)
|
|
279
|
+
for scan in scans:
|
|
280
|
+
if scan.get("target_id") == target_id:
|
|
281
|
+
return scan.get("scan_id")
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
def scan_url(self, url: str, profile: ScanProfile = ScanProfile.FULL_SCAN,
|
|
285
|
+
description: str = "") -> str:
|
|
286
|
+
"""
|
|
287
|
+
Convenience method to add target and start scan in one call.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
url: URL to scan
|
|
291
|
+
profile: Scan profile to use
|
|
292
|
+
description: Target description
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Scan ID
|
|
296
|
+
"""
|
|
297
|
+
# Check if target already exists
|
|
298
|
+
target_id = self.get_target_by_url(url)
|
|
299
|
+
if not target_id:
|
|
300
|
+
target_id = self.add_target(url, description)
|
|
301
|
+
|
|
302
|
+
return self.start_scan(target_id, profile)
|
|
303
|
+
|
|
304
|
+
def get_scan_status(self, scan_id: str) -> ScanResult:
|
|
305
|
+
"""Get current status of a scan."""
|
|
306
|
+
result = self._request("GET", f"scans/{scan_id}")
|
|
307
|
+
|
|
308
|
+
current = result.get("current_session", {})
|
|
309
|
+
target = result.get("target", {})
|
|
310
|
+
|
|
311
|
+
return ScanResult(
|
|
312
|
+
scan_id=scan_id,
|
|
313
|
+
target_id=result.get("target_id", ""),
|
|
314
|
+
target_url=target.get("address", ""),
|
|
315
|
+
status=current.get("status", "unknown"),
|
|
316
|
+
progress=current.get("progress", 0),
|
|
317
|
+
start_time=current.get("start_date"),
|
|
318
|
+
vulnerabilities=current.get("severity_counts", {}),
|
|
319
|
+
threat_level=current.get("threat", 0)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def list_scans(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
323
|
+
"""List all scans."""
|
|
324
|
+
result = self._request("GET", "scans", params={"l": limit})
|
|
325
|
+
return result.get("scans", [])
|
|
326
|
+
|
|
327
|
+
def stop_scan(self, scan_id: str) -> bool:
|
|
328
|
+
"""Stop a running scan."""
|
|
329
|
+
try:
|
|
330
|
+
self._request("POST", f"scans/{scan_id}/abort")
|
|
331
|
+
logger.info(f"Stopped scan: {scan_id}")
|
|
332
|
+
return True
|
|
333
|
+
except Exception:
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
def pause_scan(self, scan_id: str) -> bool:
|
|
337
|
+
"""Pause a running scan."""
|
|
338
|
+
try:
|
|
339
|
+
self._request("POST", f"scans/{scan_id}/pause")
|
|
340
|
+
return True
|
|
341
|
+
except Exception:
|
|
342
|
+
return False
|
|
343
|
+
|
|
344
|
+
def resume_scan(self, scan_id: str) -> bool:
|
|
345
|
+
"""Resume a paused scan."""
|
|
346
|
+
try:
|
|
347
|
+
self._request("POST", f"scans/{scan_id}/resume")
|
|
348
|
+
return True
|
|
349
|
+
except Exception:
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
def delete_scan(self, scan_id: str) -> bool:
|
|
353
|
+
"""Delete a scan."""
|
|
354
|
+
try:
|
|
355
|
+
self._request("DELETE", f"scans/{scan_id}")
|
|
356
|
+
return True
|
|
357
|
+
except Exception:
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
def wait_for_scan(self, scan_id: str, timeout: int = 3600,
|
|
361
|
+
poll_interval: int = 30,
|
|
362
|
+
callback: callable = None) -> ScanResult:
|
|
363
|
+
"""
|
|
364
|
+
Wait for a scan to complete.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
scan_id: Scan ID to wait for
|
|
368
|
+
timeout: Maximum time to wait in seconds
|
|
369
|
+
poll_interval: Time between status checks
|
|
370
|
+
callback: Optional callback function called with ScanResult on each poll
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Final ScanResult
|
|
374
|
+
"""
|
|
375
|
+
start_time = time.time()
|
|
376
|
+
while time.time() - start_time < timeout:
|
|
377
|
+
result = self.get_scan_status(scan_id)
|
|
378
|
+
|
|
379
|
+
if callback:
|
|
380
|
+
callback(result)
|
|
381
|
+
|
|
382
|
+
if result.status in [ScanStatus.COMPLETED.value,
|
|
383
|
+
ScanStatus.FAILED.value,
|
|
384
|
+
ScanStatus.ABORTED.value]:
|
|
385
|
+
return result
|
|
386
|
+
|
|
387
|
+
logger.info(f"Scan {scan_id}: {result.status} ({result.progress}%)")
|
|
388
|
+
time.sleep(poll_interval)
|
|
389
|
+
|
|
390
|
+
raise TimeoutError(f"Scan {scan_id} did not complete within {timeout}s")
|
|
391
|
+
|
|
392
|
+
# ==================== Vulnerability Management ====================
|
|
393
|
+
|
|
394
|
+
def get_vulnerabilities(self, scan_id: str = None,
|
|
395
|
+
severity: Severity = None,
|
|
396
|
+
limit: int = 100) -> List[Vulnerability]:
|
|
397
|
+
"""
|
|
398
|
+
Get vulnerabilities from scans.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
scan_id: Optional scan ID to filter by
|
|
402
|
+
severity: Optional severity filter
|
|
403
|
+
limit: Maximum results to return
|
|
404
|
+
"""
|
|
405
|
+
params = {"l": limit}
|
|
406
|
+
if severity:
|
|
407
|
+
params["q"] = f"severity:{severity.value}"
|
|
408
|
+
|
|
409
|
+
result = self._request("GET", "vulnerabilities", params=params)
|
|
410
|
+
vulns = []
|
|
411
|
+
|
|
412
|
+
for v in result.get("vulnerabilities", []):
|
|
413
|
+
vulns.append(Vulnerability(
|
|
414
|
+
vuln_id=v.get("vuln_id", ""),
|
|
415
|
+
severity=v.get("severity", "info"),
|
|
416
|
+
name=v.get("vt_name", "Unknown"),
|
|
417
|
+
description=v.get("vt_description", ""),
|
|
418
|
+
target_url=v.get("target", {}).get("address", ""),
|
|
419
|
+
affected_url=v.get("affects_url", ""),
|
|
420
|
+
recommendation=v.get("recommendation", ""),
|
|
421
|
+
cvss_score=v.get("cvss_score"),
|
|
422
|
+
cwe_id=v.get("cwe_id")
|
|
423
|
+
))
|
|
424
|
+
|
|
425
|
+
return vulns
|
|
426
|
+
|
|
427
|
+
def get_vulnerability_details(self, vuln_id: str) -> Dict[str, Any]:
|
|
428
|
+
"""Get detailed vulnerability information."""
|
|
429
|
+
return self._request("GET", f"vulnerabilities/{vuln_id}")
|
|
430
|
+
|
|
431
|
+
def get_scan_vulnerabilities(self, scan_id: str) -> List[Vulnerability]:
|
|
432
|
+
"""Get all vulnerabilities for a specific scan."""
|
|
433
|
+
# Get scan to find target
|
|
434
|
+
scan = self._request("GET", f"scans/{scan_id}")
|
|
435
|
+
target_id = scan.get("target_id")
|
|
436
|
+
|
|
437
|
+
# Get vulnerabilities for target
|
|
438
|
+
result = self._request("GET", f"targets/{target_id}/vulnerabilities")
|
|
439
|
+
vulns = []
|
|
440
|
+
|
|
441
|
+
for v in result.get("vulnerabilities", []):
|
|
442
|
+
vulns.append(Vulnerability(
|
|
443
|
+
vuln_id=v.get("vuln_id", ""),
|
|
444
|
+
severity=v.get("severity", "info"),
|
|
445
|
+
name=v.get("vt_name", "Unknown"),
|
|
446
|
+
description="", # Need to fetch details separately
|
|
447
|
+
target_url=scan.get("target", {}).get("address", ""),
|
|
448
|
+
affected_url=v.get("affects_url", "")
|
|
449
|
+
))
|
|
450
|
+
|
|
451
|
+
return vulns
|
|
452
|
+
|
|
453
|
+
# ==================== Reporting ====================
|
|
454
|
+
|
|
455
|
+
def generate_report(self, scan_id: str,
|
|
456
|
+
template: str = "11111111-1111-1111-1111-111111111111",
|
|
457
|
+
format_type: str = "pdf") -> str:
|
|
458
|
+
"""
|
|
459
|
+
Generate a report for a scan.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
scan_id: Scan ID to report on
|
|
463
|
+
template: Report template ID
|
|
464
|
+
format_type: Report format (pdf, html)
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Report ID
|
|
468
|
+
"""
|
|
469
|
+
# Get scan to find source
|
|
470
|
+
scan = self._request("GET", f"scans/{scan_id}")
|
|
471
|
+
|
|
472
|
+
data = {
|
|
473
|
+
"template_id": template,
|
|
474
|
+
"source": {
|
|
475
|
+
"list_type": "scans",
|
|
476
|
+
"id_list": [scan_id]
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
result = self._request("POST", "reports", data=data)
|
|
480
|
+
return result.get("report_id", "")
|
|
481
|
+
|
|
482
|
+
def get_report_status(self, report_id: str) -> Dict[str, Any]:
|
|
483
|
+
"""Get report generation status."""
|
|
484
|
+
return self._request("GET", f"reports/{report_id}")
|
|
485
|
+
|
|
486
|
+
def download_report(self, report_id: str, output_path: str) -> str:
|
|
487
|
+
"""
|
|
488
|
+
Download a generated report.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
report_id: Report ID
|
|
492
|
+
output_path: Path to save the report
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Path to saved report
|
|
496
|
+
"""
|
|
497
|
+
url = f"{self.api_url}/reports/{report_id}/download"
|
|
498
|
+
response = self.session.get(url, stream=True)
|
|
499
|
+
response.raise_for_status()
|
|
500
|
+
|
|
501
|
+
with open(output_path, 'wb') as f:
|
|
502
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
503
|
+
f.write(chunk)
|
|
504
|
+
|
|
505
|
+
logger.info(f"Report saved to: {output_path}")
|
|
506
|
+
return output_path
|
|
507
|
+
|
|
508
|
+
# ==================== Statistics & Dashboard ====================
|
|
509
|
+
|
|
510
|
+
def get_dashboard_stats(self) -> Dict[str, Any]:
|
|
511
|
+
"""Get dashboard statistics."""
|
|
512
|
+
try:
|
|
513
|
+
return self._request("GET", "dashboard/stats")
|
|
514
|
+
except Exception:
|
|
515
|
+
# Fallback: calculate from scans
|
|
516
|
+
scans = self.list_scans(limit=100)
|
|
517
|
+
stats = {
|
|
518
|
+
"total_scans": len(scans),
|
|
519
|
+
"completed": sum(1 for s in scans if s.get("current_session", {}).get("status") == "completed"),
|
|
520
|
+
"running": sum(1 for s in scans if s.get("current_session", {}).get("status") == "processing"),
|
|
521
|
+
"vulnerabilities": {
|
|
522
|
+
"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
for scan in scans:
|
|
526
|
+
counts = scan.get("current_session", {}).get("severity_counts", {})
|
|
527
|
+
for sev in stats["vulnerabilities"]:
|
|
528
|
+
stats["vulnerabilities"][sev] += counts.get(sev, 0)
|
|
529
|
+
return stats
|
|
530
|
+
|
|
531
|
+
def get_scan_summary(self) -> Dict[str, Any]:
|
|
532
|
+
"""Get summary of all scans with vulnerability counts."""
|
|
533
|
+
scans = self.list_scans(limit=50)
|
|
534
|
+
summary = {
|
|
535
|
+
"total_scans": len(scans),
|
|
536
|
+
"by_status": {},
|
|
537
|
+
"total_vulnerabilities": {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0},
|
|
538
|
+
"recent_scans": []
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for scan in scans:
|
|
542
|
+
status = scan.get("current_session", {}).get("status", "unknown")
|
|
543
|
+
summary["by_status"][status] = summary["by_status"].get(status, 0) + 1
|
|
544
|
+
|
|
545
|
+
counts = scan.get("current_session", {}).get("severity_counts", {})
|
|
546
|
+
for sev in summary["total_vulnerabilities"]:
|
|
547
|
+
summary["total_vulnerabilities"][sev] += counts.get(sev, 0)
|
|
548
|
+
|
|
549
|
+
if len(summary["recent_scans"]) < 10:
|
|
550
|
+
summary["recent_scans"].append({
|
|
551
|
+
"scan_id": scan.get("scan_id"),
|
|
552
|
+
"target": scan.get("target", {}).get("address"),
|
|
553
|
+
"status": status,
|
|
554
|
+
"progress": scan.get("current_session", {}).get("progress", 0),
|
|
555
|
+
"vulnerabilities": counts
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
return summary
|
|
559
|
+
|
|
560
|
+
# ==================== Orchestration Helpers ====================
|
|
561
|
+
|
|
562
|
+
def to_finding(self, vuln: Vulnerability) -> Dict[str, Any]:
|
|
563
|
+
"""Convert Acunetix vulnerability to AIPT finding format."""
|
|
564
|
+
return {
|
|
565
|
+
"type": "vulnerability",
|
|
566
|
+
"value": vuln.name,
|
|
567
|
+
"description": vuln.description or f"{vuln.name} at {vuln.affected_url}",
|
|
568
|
+
"severity": vuln.severity,
|
|
569
|
+
"phase": "scanning",
|
|
570
|
+
"tool": "acunetix",
|
|
571
|
+
"metadata": {
|
|
572
|
+
"vuln_id": vuln.vuln_id,
|
|
573
|
+
"target_url": vuln.target_url,
|
|
574
|
+
"affected_url": vuln.affected_url,
|
|
575
|
+
"cvss_score": vuln.cvss_score,
|
|
576
|
+
"cwe_id": vuln.cwe_id
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
def export_findings(self, scan_id: str, output_path: str = None) -> List[Dict[str, Any]]:
|
|
581
|
+
"""
|
|
582
|
+
Export scan findings in AIPT format.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
scan_id: Scan ID to export
|
|
586
|
+
output_path: Optional path to save JSON
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
List of findings in AIPT format
|
|
590
|
+
"""
|
|
591
|
+
vulns = self.get_scan_vulnerabilities(scan_id)
|
|
592
|
+
findings = [self.to_finding(v) for v in vulns]
|
|
593
|
+
|
|
594
|
+
if output_path:
|
|
595
|
+
with open(output_path, 'w') as f:
|
|
596
|
+
json.dump(findings, f, indent=2)
|
|
597
|
+
logger.info(f"Exported {len(findings)} findings to {output_path}")
|
|
598
|
+
|
|
599
|
+
return findings
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
# ==================== Standalone Functions for Orchestration ====================
|
|
603
|
+
|
|
604
|
+
# Global instance for quick access
|
|
605
|
+
_acunetix: Optional[AcunetixTool] = None
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def get_acunetix(config: Optional[AcunetixConfig] = None) -> AcunetixTool:
|
|
609
|
+
"""Get or create Acunetix tool instance."""
|
|
610
|
+
global _acunetix
|
|
611
|
+
if _acunetix is None or config is not None:
|
|
612
|
+
_acunetix = AcunetixTool(config)
|
|
613
|
+
_acunetix.connect()
|
|
614
|
+
return _acunetix
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def acunetix_scan(url: str, profile: str = "full") -> ScanResult:
|
|
618
|
+
"""
|
|
619
|
+
Quick scan function for orchestration.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
url: URL to scan
|
|
623
|
+
profile: Scan profile (full, high_risk, xss, sqli, crawl)
|
|
624
|
+
|
|
625
|
+
Returns:
|
|
626
|
+
ScanResult
|
|
627
|
+
"""
|
|
628
|
+
profiles = {
|
|
629
|
+
"full": ScanProfile.FULL_SCAN,
|
|
630
|
+
"high_risk": ScanProfile.HIGH_RISK,
|
|
631
|
+
"xss": ScanProfile.XSS_SCAN,
|
|
632
|
+
"sqli": ScanProfile.SQL_INJECTION,
|
|
633
|
+
"crawl": ScanProfile.CRAWL_ONLY,
|
|
634
|
+
"malware": ScanProfile.MALWARE_SCAN
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
acunetix = get_acunetix()
|
|
638
|
+
scan_id = acunetix.scan_url(url, profiles.get(profile, ScanProfile.FULL_SCAN))
|
|
639
|
+
return acunetix.get_scan_status(scan_id)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def acunetix_status(scan_id: str) -> ScanResult:
|
|
643
|
+
"""Get scan status."""
|
|
644
|
+
return get_acunetix().get_scan_status(scan_id)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def acunetix_vulns(scan_id: str = None, severity: str = None) -> List[Dict[str, Any]]:
|
|
648
|
+
"""Get vulnerabilities as AIPT findings."""
|
|
649
|
+
acunetix = get_acunetix()
|
|
650
|
+
sev = Severity(severity) if severity else None
|
|
651
|
+
vulns = acunetix.get_vulnerabilities(scan_id, sev)
|
|
652
|
+
return [acunetix.to_finding(v) for v in vulns]
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def acunetix_summary() -> Dict[str, Any]:
|
|
656
|
+
"""Get scan summary."""
|
|
657
|
+
return get_acunetix().get_scan_summary()
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
# ==================== CLI for Testing ====================
|
|
661
|
+
|
|
662
|
+
if __name__ == "__main__":
|
|
663
|
+
import argparse
|
|
664
|
+
|
|
665
|
+
parser = argparse.ArgumentParser(description="Acunetix Scanner Tool")
|
|
666
|
+
parser.add_argument("command", choices=["test", "scan", "status", "vulns", "summary"])
|
|
667
|
+
parser.add_argument("--url", help="Target URL for scanning")
|
|
668
|
+
parser.add_argument("--scan-id", help="Scan ID for status/vulns")
|
|
669
|
+
parser.add_argument("--profile", default="full", help="Scan profile")
|
|
670
|
+
|
|
671
|
+
args = parser.parse_args()
|
|
672
|
+
|
|
673
|
+
acunetix = get_acunetix()
|
|
674
|
+
|
|
675
|
+
if args.command == "test":
|
|
676
|
+
print("Testing Acunetix connection...")
|
|
677
|
+
if acunetix.connect():
|
|
678
|
+
info = acunetix.get_info()
|
|
679
|
+
print(f"✓ Connected as: {info.get('email')}")
|
|
680
|
+
print(f"✓ Access: {info.get('access_rights')}")
|
|
681
|
+
else:
|
|
682
|
+
print("✗ Connection failed")
|
|
683
|
+
|
|
684
|
+
elif args.command == "scan":
|
|
685
|
+
if not args.url:
|
|
686
|
+
print("Error: --url required for scan")
|
|
687
|
+
else:
|
|
688
|
+
result = acunetix_scan(args.url, args.profile)
|
|
689
|
+
print(f"Scan started: {result.scan_id}")
|
|
690
|
+
print(f"Status: {result.status}")
|
|
691
|
+
|
|
692
|
+
elif args.command == "status":
|
|
693
|
+
if not args.scan_id:
|
|
694
|
+
print("Error: --scan-id required")
|
|
695
|
+
else:
|
|
696
|
+
result = acunetix_status(args.scan_id)
|
|
697
|
+
print(f"Scan: {result.scan_id}")
|
|
698
|
+
print(f"Target: {result.target_url}")
|
|
699
|
+
print(f"Status: {result.status} ({result.progress}%)")
|
|
700
|
+
print(f"Vulnerabilities: {result.vulnerabilities}")
|
|
701
|
+
|
|
702
|
+
elif args.command == "vulns":
|
|
703
|
+
findings = acunetix_vulns(args.scan_id)
|
|
704
|
+
print(f"Found {len(findings)} vulnerabilities:")
|
|
705
|
+
for f in findings[:10]:
|
|
706
|
+
print(f" [{f['severity'].upper()}] {f['value']}")
|
|
707
|
+
|
|
708
|
+
elif args.command == "summary":
|
|
709
|
+
summary = acunetix_summary()
|
|
710
|
+
print(f"Total Scans: {summary['total_scans']}")
|
|
711
|
+
print(f"By Status: {summary['by_status']}")
|
|
712
|
+
print(f"Total Vulnerabilities: {summary['total_vulnerabilities']}")
|