aiptx 2.0.2__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.
Potentially problematic release.
This version of aiptx might be problematic. Click here for more details.
- 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 +24 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +960 -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 +321 -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 +288 -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 +85 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -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/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/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 +2284 -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 +44 -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/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/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 +201 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aiptx-2.0.2.dist-info/METADATA +324 -0
- aiptx-2.0.2.dist-info/RECORD +165 -0
- aiptx-2.0.2.dist-info/WHEEL +5 -0
- aiptx-2.0.2.dist-info/entry_points.txt +7 -0
- aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Burp Suite Scanner Tool - Plug & Play Integration for AIPT Orchestration
|
|
4
|
+
Provides comprehensive API integration with Burp Suite Pro REST API.
|
|
5
|
+
|
|
6
|
+
Burp Suite Pro REST API Endpoints:
|
|
7
|
+
PUT /configuration - Configure Burp settings
|
|
8
|
+
GET /issue_definitions - Get all issue definitions
|
|
9
|
+
POST /scan - Start a new scan
|
|
10
|
+
GET /scan/[task_id] - Get scan progress and issues
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import time
|
|
15
|
+
import logging
|
|
16
|
+
import requests
|
|
17
|
+
from typing import Optional, Dict, List, Any
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
import urllib3
|
|
23
|
+
|
|
24
|
+
# Disable SSL warnings for self-signed certificates
|
|
25
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ScanStatus(Enum):
|
|
31
|
+
"""Burp Suite scan status types."""
|
|
32
|
+
QUEUED = "queued"
|
|
33
|
+
RUNNING = "running"
|
|
34
|
+
PAUSED = "paused"
|
|
35
|
+
SUCCEEDED = "succeeded"
|
|
36
|
+
FAILED = "failed"
|
|
37
|
+
CANCELLED = "cancelled"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class IssueSeverity(Enum):
|
|
41
|
+
"""Vulnerability severity levels in Burp."""
|
|
42
|
+
HIGH = "high"
|
|
43
|
+
MEDIUM = "medium"
|
|
44
|
+
LOW = "low"
|
|
45
|
+
INFO = "info"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class IssueConfidence(Enum):
|
|
49
|
+
"""Issue confidence levels in Burp."""
|
|
50
|
+
CERTAIN = "certain"
|
|
51
|
+
FIRM = "firm"
|
|
52
|
+
TENTATIVE = "tentative"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class BurpConfig:
|
|
57
|
+
"""Configuration for Burp Suite connection."""
|
|
58
|
+
base_url: str = "http://13.127.28.41:1337/v0.1"
|
|
59
|
+
api_key: str = "t7thBWbImyiP8SA9hojkiFhq9QbHqlcm"
|
|
60
|
+
verify_ssl: bool = False
|
|
61
|
+
timeout: int = 30
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ScanResult:
|
|
66
|
+
"""Result of a Burp Suite scan."""
|
|
67
|
+
task_id: str
|
|
68
|
+
target_url: str
|
|
69
|
+
status: str
|
|
70
|
+
request_count: int = 0
|
|
71
|
+
error_count: int = 0
|
|
72
|
+
insertion_point_count: int = 0
|
|
73
|
+
issue_events: List[Dict] = field(default_factory=list)
|
|
74
|
+
audit_items: List[Dict] = field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class Issue:
|
|
79
|
+
"""Security issue/vulnerability from Burp Suite."""
|
|
80
|
+
issue_type: str
|
|
81
|
+
name: str
|
|
82
|
+
severity: str
|
|
83
|
+
confidence: str
|
|
84
|
+
host: str
|
|
85
|
+
path: str
|
|
86
|
+
origin: str
|
|
87
|
+
description: str = ""
|
|
88
|
+
remediation: str = ""
|
|
89
|
+
serial_number: str = ""
|
|
90
|
+
evidence: List[Dict] = field(default_factory=list)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class BurpTool:
|
|
94
|
+
"""
|
|
95
|
+
Burp Suite Scanner Tool for AIPT Orchestration.
|
|
96
|
+
|
|
97
|
+
Provides plug-and-play integration with Burp Suite Pro REST API extension.
|
|
98
|
+
Supports scan execution, issue retrieval, and configuration.
|
|
99
|
+
|
|
100
|
+
API Documentation: http://[server]:1337/ (HTML interface)
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, config: Optional[BurpConfig] = None):
|
|
104
|
+
"""Initialize Burp Suite tool with configuration."""
|
|
105
|
+
self.config = config or BurpConfig()
|
|
106
|
+
self.session = requests.Session()
|
|
107
|
+
self._setup_session()
|
|
108
|
+
self._connected = False
|
|
109
|
+
self._issue_definitions: Dict[str, Dict] = {}
|
|
110
|
+
|
|
111
|
+
def _setup_session(self):
|
|
112
|
+
"""Setup the requests session."""
|
|
113
|
+
headers = {
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
"Accept": "application/json"
|
|
116
|
+
}
|
|
117
|
+
# Burp REST API uses API key directly in Authorization header
|
|
118
|
+
if self.config.api_key:
|
|
119
|
+
headers["Authorization"] = self.config.api_key
|
|
120
|
+
self.session.headers.update(headers)
|
|
121
|
+
self.session.verify = self.config.verify_ssl
|
|
122
|
+
|
|
123
|
+
def _request(self, method: str, endpoint: str, data: Optional[Dict] = None,
|
|
124
|
+
params: Optional[Dict] = None) -> Any:
|
|
125
|
+
"""Make an API request to Burp Suite."""
|
|
126
|
+
# Ensure base_url doesn't have trailing slash and endpoint doesn't have leading slash
|
|
127
|
+
base = self.config.base_url.rstrip('/')
|
|
128
|
+
ep = endpoint.lstrip('/')
|
|
129
|
+
url = f"{base}/{ep}"
|
|
130
|
+
try:
|
|
131
|
+
response = self.session.request(
|
|
132
|
+
method=method,
|
|
133
|
+
url=url,
|
|
134
|
+
json=data,
|
|
135
|
+
params=params,
|
|
136
|
+
timeout=self.config.timeout
|
|
137
|
+
)
|
|
138
|
+
response.raise_for_status()
|
|
139
|
+
|
|
140
|
+
# Handle different response types
|
|
141
|
+
if response.content:
|
|
142
|
+
content_type = response.headers.get('Content-Type', '')
|
|
143
|
+
if 'application/json' in content_type:
|
|
144
|
+
return response.json()
|
|
145
|
+
return response.text
|
|
146
|
+
|
|
147
|
+
# For 201/202 responses, return location header
|
|
148
|
+
if response.status_code in [201, 202]:
|
|
149
|
+
location = response.headers.get('Location', '')
|
|
150
|
+
return {"success": True, "location": location, "task_id": location.split('/')[-1] if location else ""}
|
|
151
|
+
|
|
152
|
+
return {"success": True}
|
|
153
|
+
except requests.exceptions.RequestException as e:
|
|
154
|
+
logger.error(f"Burp Suite API error: {e}")
|
|
155
|
+
raise
|
|
156
|
+
|
|
157
|
+
# ==================== Connection ====================
|
|
158
|
+
|
|
159
|
+
def connect(self) -> bool:
|
|
160
|
+
"""Test connection to Burp Suite."""
|
|
161
|
+
try:
|
|
162
|
+
# Check if server is reachable by hitting the versioned endpoint
|
|
163
|
+
response = self.session.get(f"{self.config.base_url}/", timeout=self.config.timeout)
|
|
164
|
+
|
|
165
|
+
# Check for Burp version header (present on all responses)
|
|
166
|
+
burp_version = response.headers.get('X-Burp-Version', '')
|
|
167
|
+
|
|
168
|
+
if not burp_version:
|
|
169
|
+
# Try root URL for version header
|
|
170
|
+
root_url = self.config.base_url.replace('/v0.1', '').replace('/v1', '')
|
|
171
|
+
root_response = self.session.get(root_url, timeout=self.config.timeout)
|
|
172
|
+
burp_version = root_response.headers.get('X-Burp-Version', '')
|
|
173
|
+
|
|
174
|
+
if burp_version:
|
|
175
|
+
logger.info(f"Burp Suite version: {burp_version}")
|
|
176
|
+
self._connected = True
|
|
177
|
+
|
|
178
|
+
# Try to get issue definitions (optional - not all versions support this)
|
|
179
|
+
try:
|
|
180
|
+
result = self._request("GET", "issue_definitions")
|
|
181
|
+
if isinstance(result, list):
|
|
182
|
+
for issue_def in result:
|
|
183
|
+
type_index = issue_def.get("type_index", "")
|
|
184
|
+
if type_index:
|
|
185
|
+
self._issue_definitions[type_index] = issue_def
|
|
186
|
+
logger.info(f"Loaded {len(self._issue_definitions)} issue definitions")
|
|
187
|
+
except Exception:
|
|
188
|
+
# issue_definitions not available in this version - that's OK
|
|
189
|
+
logger.info("Issue definitions endpoint not available (normal for some versions)")
|
|
190
|
+
|
|
191
|
+
logger.info(f"Connected to Burp Suite API at {self.config.base_url}")
|
|
192
|
+
return True
|
|
193
|
+
else:
|
|
194
|
+
logger.error("No X-Burp-Version header found - server may not be Burp Suite")
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
except requests.exceptions.ConnectionError as e:
|
|
198
|
+
logger.error(f"Cannot connect to Burp Suite at {self.config.base_url}: {e}")
|
|
199
|
+
return False
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"Failed to connect to Burp Suite: {e}")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def get_info(self) -> Dict[str, Any]:
|
|
205
|
+
"""Get Burp Suite instance information."""
|
|
206
|
+
return {
|
|
207
|
+
"server": self.config.base_url,
|
|
208
|
+
"connected": self._connected,
|
|
209
|
+
"issue_definitions_loaded": len(self._issue_definitions),
|
|
210
|
+
"type": "Burp Suite Pro REST API"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
def is_connected(self) -> bool:
|
|
214
|
+
"""Check if connected to Burp Suite."""
|
|
215
|
+
return self._connected
|
|
216
|
+
|
|
217
|
+
# ==================== Configuration ====================
|
|
218
|
+
|
|
219
|
+
def configure(self, config_data: Dict[str, Any]) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Configure Burp Suite settings.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
config_data: Configuration dictionary (see Burp API docs)
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Success status
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
self._request("PUT", "/configuration", data=config_data)
|
|
231
|
+
logger.info("Burp configuration updated")
|
|
232
|
+
return True
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(f"Failed to configure Burp: {e}")
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
def set_scope(self, urls: List[str], exclude_urls: List[str] = None) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Set target scope in Burp.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
urls: URLs to include in scope
|
|
243
|
+
exclude_urls: URLs to exclude from scope
|
|
244
|
+
"""
|
|
245
|
+
config = {
|
|
246
|
+
"target": {
|
|
247
|
+
"scope": {
|
|
248
|
+
"include": [{"enabled": True, "prefix": url} for url in urls],
|
|
249
|
+
"exclude": [{"enabled": True, "prefix": url} for url in (exclude_urls or [])]
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return self.configure(config)
|
|
254
|
+
|
|
255
|
+
# ==================== Issue Definitions ====================
|
|
256
|
+
|
|
257
|
+
def get_issue_definitions(self) -> List[Dict[str, Any]]:
|
|
258
|
+
"""Get all Burp issue definitions."""
|
|
259
|
+
if not self._issue_definitions:
|
|
260
|
+
result = self._request("GET", "/issue_definitions")
|
|
261
|
+
if isinstance(result, list):
|
|
262
|
+
for issue_def in result:
|
|
263
|
+
type_index = issue_def.get("type_index", "")
|
|
264
|
+
if type_index:
|
|
265
|
+
self._issue_definitions[type_index] = issue_def
|
|
266
|
+
return list(self._issue_definitions.values())
|
|
267
|
+
|
|
268
|
+
def get_issue_definition(self, type_index: str) -> Optional[Dict[str, Any]]:
|
|
269
|
+
"""Get a specific issue definition by type."""
|
|
270
|
+
if not self._issue_definitions:
|
|
271
|
+
self.get_issue_definitions()
|
|
272
|
+
return self._issue_definitions.get(type_index)
|
|
273
|
+
|
|
274
|
+
# ==================== Scan Management ====================
|
|
275
|
+
|
|
276
|
+
def start_scan(self, url: str, scope: List[str] = None,
|
|
277
|
+
configuration: Dict = None) -> str:
|
|
278
|
+
"""
|
|
279
|
+
Start a new scan.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
url: Target URL to scan
|
|
283
|
+
scope: Additional URLs for scope (optional)
|
|
284
|
+
configuration: Scan configuration (optional)
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Task ID for the scan
|
|
288
|
+
"""
|
|
289
|
+
scan_data = {
|
|
290
|
+
"urls": [url] + (scope or [])
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if configuration:
|
|
294
|
+
scan_data["configuration"] = configuration
|
|
295
|
+
|
|
296
|
+
result = self._request("POST", "/scan", data=scan_data)
|
|
297
|
+
|
|
298
|
+
task_id = ""
|
|
299
|
+
if isinstance(result, dict):
|
|
300
|
+
task_id = result.get("task_id", "")
|
|
301
|
+
if not task_id and result.get("location"):
|
|
302
|
+
task_id = result["location"].split("/")[-1]
|
|
303
|
+
|
|
304
|
+
logger.info(f"Started scan: {task_id} for {url}")
|
|
305
|
+
return task_id
|
|
306
|
+
|
|
307
|
+
def scan_url(self, url: str) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Convenience method to scan a URL directly.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
url: URL to scan
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Task ID
|
|
316
|
+
"""
|
|
317
|
+
return self.start_scan(url)
|
|
318
|
+
|
|
319
|
+
def get_scan_status(self, task_id: str, after: str = None,
|
|
320
|
+
issue_events: int = None) -> ScanResult:
|
|
321
|
+
"""
|
|
322
|
+
Get current status of a scan.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
task_id: Scan task ID
|
|
326
|
+
after: Return events after this marker (for incremental updates)
|
|
327
|
+
issue_events: Maximum number of issue events to return
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
ScanResult with current status
|
|
331
|
+
"""
|
|
332
|
+
params = {}
|
|
333
|
+
if after:
|
|
334
|
+
params["after"] = after
|
|
335
|
+
if issue_events:
|
|
336
|
+
params["issue_events"] = issue_events
|
|
337
|
+
|
|
338
|
+
result = self._request("GET", f"/scan/{task_id}", params=params)
|
|
339
|
+
|
|
340
|
+
if not isinstance(result, dict):
|
|
341
|
+
result = {}
|
|
342
|
+
|
|
343
|
+
return ScanResult(
|
|
344
|
+
task_id=task_id,
|
|
345
|
+
target_url=result.get("scan_metrics", {}).get("crawl_and_audit_urls", [""])[0] if result.get("scan_metrics") else "",
|
|
346
|
+
status=result.get("scan_status", "unknown"),
|
|
347
|
+
request_count=result.get("scan_metrics", {}).get("request_count", 0),
|
|
348
|
+
error_count=result.get("scan_metrics", {}).get("crawl_and_audit_error_count", 0),
|
|
349
|
+
insertion_point_count=result.get("scan_metrics", {}).get("insertion_point_count", 0),
|
|
350
|
+
issue_events=result.get("issue_events", []),
|
|
351
|
+
audit_items=result.get("audit_items", [])
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def wait_for_scan(self, task_id: str, timeout: int = 3600,
|
|
355
|
+
poll_interval: int = 30,
|
|
356
|
+
callback: callable = None) -> ScanResult:
|
|
357
|
+
"""
|
|
358
|
+
Wait for a scan to complete.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
task_id: Task ID to wait for
|
|
362
|
+
timeout: Maximum time to wait in seconds
|
|
363
|
+
poll_interval: Time between status checks
|
|
364
|
+
callback: Optional callback function
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Final ScanResult
|
|
368
|
+
"""
|
|
369
|
+
start_time = time.time()
|
|
370
|
+
terminal_statuses = ["succeeded", "failed", "cancelled"]
|
|
371
|
+
|
|
372
|
+
while time.time() - start_time < timeout:
|
|
373
|
+
result = self.get_scan_status(task_id)
|
|
374
|
+
|
|
375
|
+
if callback:
|
|
376
|
+
callback(result)
|
|
377
|
+
|
|
378
|
+
if result.status.lower() in terminal_statuses:
|
|
379
|
+
return result
|
|
380
|
+
|
|
381
|
+
logger.info(f"Scan {task_id}: {result.status} (requests: {result.request_count})")
|
|
382
|
+
time.sleep(poll_interval)
|
|
383
|
+
|
|
384
|
+
raise TimeoutError(f"Scan {task_id} did not complete within {timeout}s")
|
|
385
|
+
|
|
386
|
+
# ==================== Issue Management ====================
|
|
387
|
+
|
|
388
|
+
def get_issues(self, task_id: str) -> List[Issue]:
|
|
389
|
+
"""
|
|
390
|
+
Get issues from a scan.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
task_id: Scan task ID
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
List of Issue objects
|
|
397
|
+
"""
|
|
398
|
+
result = self.get_scan_status(task_id, issue_events=1000)
|
|
399
|
+
issues = []
|
|
400
|
+
|
|
401
|
+
for event in result.issue_events:
|
|
402
|
+
issue_data = event.get("issue", {})
|
|
403
|
+
type_index = issue_data.get("type_index", "")
|
|
404
|
+
|
|
405
|
+
# Get issue definition for name and description
|
|
406
|
+
issue_def = self.get_issue_definition(type_index) or {}
|
|
407
|
+
|
|
408
|
+
issues.append(Issue(
|
|
409
|
+
issue_type=type_index,
|
|
410
|
+
name=issue_def.get("name", issue_data.get("name", "Unknown")),
|
|
411
|
+
severity=issue_data.get("severity", "info"),
|
|
412
|
+
confidence=issue_data.get("confidence", "tentative"),
|
|
413
|
+
host=issue_data.get("origin", ""),
|
|
414
|
+
path=issue_data.get("path", ""),
|
|
415
|
+
origin=issue_data.get("origin", ""),
|
|
416
|
+
description=issue_def.get("description", ""),
|
|
417
|
+
remediation=issue_def.get("remediation", ""),
|
|
418
|
+
serial_number=str(issue_data.get("serial_number", "")),
|
|
419
|
+
evidence=issue_data.get("evidence", [])
|
|
420
|
+
))
|
|
421
|
+
|
|
422
|
+
return issues
|
|
423
|
+
|
|
424
|
+
def get_all_issues(self, task_id: str) -> List[Dict[str, Any]]:
|
|
425
|
+
"""Get all issues with full details for a scan."""
|
|
426
|
+
issues = self.get_issues(task_id)
|
|
427
|
+
return [self.to_finding(i) for i in issues]
|
|
428
|
+
|
|
429
|
+
# ==================== Statistics ====================
|
|
430
|
+
|
|
431
|
+
def get_scan_summary(self, task_id: str = None) -> Dict[str, Any]:
|
|
432
|
+
"""Get scan summary."""
|
|
433
|
+
if not task_id:
|
|
434
|
+
return {
|
|
435
|
+
"connected": self._connected,
|
|
436
|
+
"issue_definitions": len(self._issue_definitions),
|
|
437
|
+
"message": "Provide task_id for scan-specific summary"
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
result = self.get_scan_status(task_id, issue_events=1000)
|
|
441
|
+
|
|
442
|
+
# Count issues by severity
|
|
443
|
+
severity_counts = {"high": 0, "medium": 0, "low": 0, "info": 0}
|
|
444
|
+
for event in result.issue_events:
|
|
445
|
+
sev = event.get("issue", {}).get("severity", "info").lower()
|
|
446
|
+
if sev in severity_counts:
|
|
447
|
+
severity_counts[sev] += 1
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
"task_id": task_id,
|
|
451
|
+
"status": result.status,
|
|
452
|
+
"request_count": result.request_count,
|
|
453
|
+
"error_count": result.error_count,
|
|
454
|
+
"issue_count": len(result.issue_events),
|
|
455
|
+
"issues_by_severity": severity_counts
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# ==================== Orchestration Helpers ====================
|
|
459
|
+
|
|
460
|
+
def to_finding(self, issue: Issue) -> Dict[str, Any]:
|
|
461
|
+
"""Convert Burp issue to AIPT finding format."""
|
|
462
|
+
return {
|
|
463
|
+
"type": "vulnerability",
|
|
464
|
+
"value": issue.name,
|
|
465
|
+
"description": issue.description or f"{issue.name} at {issue.path}",
|
|
466
|
+
"severity": issue.severity,
|
|
467
|
+
"phase": "scanning",
|
|
468
|
+
"tool": "burpsuite",
|
|
469
|
+
"metadata": {
|
|
470
|
+
"issue_type": issue.issue_type,
|
|
471
|
+
"confidence": issue.confidence,
|
|
472
|
+
"host": issue.host,
|
|
473
|
+
"path": issue.path,
|
|
474
|
+
"origin": issue.origin,
|
|
475
|
+
"remediation": issue.remediation,
|
|
476
|
+
"serial_number": issue.serial_number
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
def export_findings(self, task_id: str, output_path: str = None) -> List[Dict[str, Any]]:
|
|
481
|
+
"""
|
|
482
|
+
Export scan findings in AIPT format.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
task_id: Task ID to export
|
|
486
|
+
output_path: Optional path to save JSON
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
List of findings in AIPT format
|
|
490
|
+
"""
|
|
491
|
+
issues = self.get_issues(task_id)
|
|
492
|
+
findings = [self.to_finding(i) for i in issues]
|
|
493
|
+
|
|
494
|
+
if output_path:
|
|
495
|
+
with open(output_path, 'w') as f:
|
|
496
|
+
json.dump(findings, f, indent=2)
|
|
497
|
+
logger.info(f"Exported {len(findings)} findings to {output_path}")
|
|
498
|
+
|
|
499
|
+
return findings
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
# ==================== Standalone Functions for Orchestration ====================
|
|
503
|
+
|
|
504
|
+
# Global instance for quick access
|
|
505
|
+
_burp: Optional[BurpTool] = None
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def get_burp(config: Optional[BurpConfig] = None) -> BurpTool:
|
|
509
|
+
"""Get or create Burp Suite tool instance."""
|
|
510
|
+
global _burp
|
|
511
|
+
if _burp is None or config is not None:
|
|
512
|
+
_burp = BurpTool(config)
|
|
513
|
+
_burp.connect()
|
|
514
|
+
return _burp
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def burp_scan(url: str) -> ScanResult:
|
|
518
|
+
"""
|
|
519
|
+
Quick scan function for orchestration.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
url: URL to scan
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
ScanResult
|
|
526
|
+
"""
|
|
527
|
+
burp = get_burp()
|
|
528
|
+
task_id = burp.scan_url(url)
|
|
529
|
+
return burp.get_scan_status(task_id)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def burp_status(task_id: str) -> ScanResult:
|
|
533
|
+
"""Get scan status."""
|
|
534
|
+
return get_burp().get_scan_status(task_id)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def burp_issues(task_id: str = None, severity: str = None) -> List[Dict[str, Any]]:
|
|
538
|
+
"""Get issues as AIPT findings."""
|
|
539
|
+
if not task_id:
|
|
540
|
+
return []
|
|
541
|
+
burp = get_burp()
|
|
542
|
+
issues = burp.get_issues(task_id)
|
|
543
|
+
findings = [burp.to_finding(i) for i in issues]
|
|
544
|
+
|
|
545
|
+
# Filter by severity if specified
|
|
546
|
+
if severity:
|
|
547
|
+
findings = [f for f in findings if f.get("severity", "").lower() == severity.lower()]
|
|
548
|
+
|
|
549
|
+
return findings
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def burp_summary(task_id: str = None) -> Dict[str, Any]:
|
|
553
|
+
"""Get scan summary."""
|
|
554
|
+
return get_burp().get_scan_summary(task_id)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
# ==================== CLI for Testing ====================
|
|
558
|
+
|
|
559
|
+
if __name__ == "__main__":
|
|
560
|
+
import argparse
|
|
561
|
+
|
|
562
|
+
parser = argparse.ArgumentParser(description="Burp Suite Scanner Tool")
|
|
563
|
+
parser.add_argument("command", choices=["test", "scan", "status", "issues", "summary", "definitions"])
|
|
564
|
+
parser.add_argument("--url", help="Target URL for scanning")
|
|
565
|
+
parser.add_argument("--task-id", help="Task ID for status/issues")
|
|
566
|
+
|
|
567
|
+
args = parser.parse_args()
|
|
568
|
+
|
|
569
|
+
config = BurpConfig()
|
|
570
|
+
burp = BurpTool(config)
|
|
571
|
+
|
|
572
|
+
if args.command == "test":
|
|
573
|
+
print("Testing Burp Suite connection...")
|
|
574
|
+
print(f"Server: {config.base_url}")
|
|
575
|
+
if burp.connect():
|
|
576
|
+
info = burp.get_info()
|
|
577
|
+
print(f"✓ Connected to Burp Suite Pro")
|
|
578
|
+
print(f"✓ Issue definitions loaded: {info.get('issue_definitions_loaded')}")
|
|
579
|
+
else:
|
|
580
|
+
print("✗ Connection failed")
|
|
581
|
+
|
|
582
|
+
elif args.command == "definitions":
|
|
583
|
+
print("Loading issue definitions...")
|
|
584
|
+
if burp.connect():
|
|
585
|
+
defs = burp.get_issue_definitions()
|
|
586
|
+
print(f"Found {len(defs)} issue definitions:")
|
|
587
|
+
for d in defs[:10]:
|
|
588
|
+
print(f" - [{d.get('type_index')}] {d.get('name')}")
|
|
589
|
+
if len(defs) > 10:
|
|
590
|
+
print(f" ... and {len(defs) - 10} more")
|
|
591
|
+
|
|
592
|
+
elif args.command == "scan":
|
|
593
|
+
if not args.url:
|
|
594
|
+
print("Error: --url required for scan")
|
|
595
|
+
else:
|
|
596
|
+
if burp.connect():
|
|
597
|
+
task_id = burp.scan_url(args.url)
|
|
598
|
+
print(f"Scan started!")
|
|
599
|
+
print(f"Task ID: {task_id}")
|
|
600
|
+
print(f"Check status: python burp_tool.py status --task-id {task_id}")
|
|
601
|
+
|
|
602
|
+
elif args.command == "status":
|
|
603
|
+
if not args.task_id:
|
|
604
|
+
print("Error: --task-id required")
|
|
605
|
+
else:
|
|
606
|
+
if burp.connect():
|
|
607
|
+
result = burp.get_scan_status(args.task_id)
|
|
608
|
+
print(f"Task ID: {result.task_id}")
|
|
609
|
+
print(f"Status: {result.status}")
|
|
610
|
+
print(f"Requests: {result.request_count}")
|
|
611
|
+
print(f"Errors: {result.error_count}")
|
|
612
|
+
print(f"Issues found: {len(result.issue_events)}")
|
|
613
|
+
|
|
614
|
+
elif args.command == "issues":
|
|
615
|
+
if not args.task_id:
|
|
616
|
+
print("Error: --task-id required")
|
|
617
|
+
else:
|
|
618
|
+
if burp.connect():
|
|
619
|
+
findings = burp.get_all_issues(args.task_id)
|
|
620
|
+
print(f"Found {len(findings)} issues:")
|
|
621
|
+
for f in findings[:10]:
|
|
622
|
+
print(f" [{f['severity'].upper()}] {f['value']}")
|
|
623
|
+
if len(findings) > 10:
|
|
624
|
+
print(f" ... and {len(findings) - 10} more")
|
|
625
|
+
|
|
626
|
+
elif args.command == "summary":
|
|
627
|
+
if burp.connect():
|
|
628
|
+
summary = burp.get_scan_summary(args.task_id)
|
|
629
|
+
print("Burp Suite Summary:")
|
|
630
|
+
for k, v in summary.items():
|
|
631
|
+
print(f" {k}: {v}")
|