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,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BloodHound Integration
|
|
3
|
+
|
|
4
|
+
Wrapper for BloodHound Python ingestor (bloodhound-python).
|
|
5
|
+
Collects Active Directory data for attack path analysis:
|
|
6
|
+
- Users, Groups, Computers
|
|
7
|
+
- ACLs and permissions
|
|
8
|
+
- Sessions and logged-on users
|
|
9
|
+
- Trust relationships
|
|
10
|
+
- Group Policy Objects
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from aipt_v2.tools.active_directory import BloodHoundWrapper
|
|
14
|
+
|
|
15
|
+
bh = BloodHoundWrapper(config)
|
|
16
|
+
result = await bh.collect()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import List, Dict, Any, Optional
|
|
25
|
+
|
|
26
|
+
from aipt_v2.tools.active_directory.ad_config import ADConfig, get_ad_config
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class BloodHoundResult:
|
|
31
|
+
"""Result of BloodHound collection."""
|
|
32
|
+
domain: str
|
|
33
|
+
status: str
|
|
34
|
+
started_at: str
|
|
35
|
+
finished_at: str
|
|
36
|
+
duration: float
|
|
37
|
+
output_files: List[str]
|
|
38
|
+
collection_methods: List[str]
|
|
39
|
+
statistics: Dict[str, int]
|
|
40
|
+
attack_paths: List[Dict] # Parsed attack paths if available
|
|
41
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class BloodHoundConfig:
|
|
46
|
+
"""BloodHound collection configuration."""
|
|
47
|
+
# Collection methods
|
|
48
|
+
collect_all: bool = True
|
|
49
|
+
collect_users: bool = True
|
|
50
|
+
collect_groups: bool = True
|
|
51
|
+
collect_computers: bool = True
|
|
52
|
+
collect_sessions: bool = True
|
|
53
|
+
collect_acls: bool = True
|
|
54
|
+
collect_trusts: bool = True
|
|
55
|
+
collect_gpos: bool = True
|
|
56
|
+
|
|
57
|
+
# Output options
|
|
58
|
+
output_dir: str = "./bloodhound_output"
|
|
59
|
+
output_prefix: str = ""
|
|
60
|
+
compress: bool = True # Create ZIP file
|
|
61
|
+
|
|
62
|
+
# Performance
|
|
63
|
+
threads: int = 10
|
|
64
|
+
dns_tcp: bool = False
|
|
65
|
+
dns_timeout: int = 3
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class BloodHoundWrapper:
|
|
69
|
+
"""
|
|
70
|
+
BloodHound Python Ingestor Wrapper.
|
|
71
|
+
|
|
72
|
+
Collects Active Directory data for BloodHound
|
|
73
|
+
attack path analysis.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
COLLECTION_METHODS = {
|
|
77
|
+
"all": "All collection methods",
|
|
78
|
+
"default": "Users, Groups, Computers, Sessions, Trusts, ACL",
|
|
79
|
+
"users": "User enumeration",
|
|
80
|
+
"groups": "Group enumeration",
|
|
81
|
+
"computers": "Computer enumeration",
|
|
82
|
+
"sessions": "Session enumeration",
|
|
83
|
+
"acl": "ACL enumeration",
|
|
84
|
+
"trusts": "Trust enumeration",
|
|
85
|
+
"gpo": "GPO enumeration",
|
|
86
|
+
"container": "Container enumeration"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
ad_config: Optional[ADConfig] = None,
|
|
92
|
+
bh_config: Optional[BloodHoundConfig] = None
|
|
93
|
+
):
|
|
94
|
+
"""
|
|
95
|
+
Initialize BloodHound wrapper.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
ad_config: AD configuration
|
|
99
|
+
bh_config: BloodHound collection configuration
|
|
100
|
+
"""
|
|
101
|
+
self.ad_config = ad_config or ADConfig()
|
|
102
|
+
self.bh_config = bh_config or BloodHoundConfig()
|
|
103
|
+
self.output_files: List[str] = []
|
|
104
|
+
self._installed = None
|
|
105
|
+
|
|
106
|
+
async def check_installed(self) -> bool:
|
|
107
|
+
"""Check if bloodhound-python is installed."""
|
|
108
|
+
if self._installed is not None:
|
|
109
|
+
return self._installed
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
process = await asyncio.create_subprocess_shell(
|
|
113
|
+
"bloodhound-python --help",
|
|
114
|
+
stdout=asyncio.subprocess.PIPE,
|
|
115
|
+
stderr=asyncio.subprocess.PIPE
|
|
116
|
+
)
|
|
117
|
+
await process.communicate()
|
|
118
|
+
self._installed = process.returncode == 0
|
|
119
|
+
except Exception:
|
|
120
|
+
self._installed = False
|
|
121
|
+
|
|
122
|
+
return self._installed
|
|
123
|
+
|
|
124
|
+
def _build_command(self) -> List[str]:
|
|
125
|
+
"""Build bloodhound-python command."""
|
|
126
|
+
cmd = ["bloodhound-python"]
|
|
127
|
+
|
|
128
|
+
# Domain and credentials
|
|
129
|
+
cmd.extend(["-d", self.ad_config.domain])
|
|
130
|
+
cmd.extend(["-u", self.ad_config.credentials.username])
|
|
131
|
+
|
|
132
|
+
if self.ad_config.credentials.password:
|
|
133
|
+
cmd.extend(["-p", self.ad_config.credentials.password])
|
|
134
|
+
elif self.ad_config.credentials.ntlm_hash:
|
|
135
|
+
# Use hash auth
|
|
136
|
+
hashes = self.ad_config.credentials.ntlm_hash
|
|
137
|
+
if ":" not in hashes:
|
|
138
|
+
hashes = f"aad3b435b51404eeaad3b435b51404ee:{hashes}"
|
|
139
|
+
cmd.extend(["--hashes", hashes])
|
|
140
|
+
|
|
141
|
+
# Target DC
|
|
142
|
+
if self.ad_config.dc_ip:
|
|
143
|
+
cmd.extend(["-dc", self.ad_config.dc_ip])
|
|
144
|
+
cmd.extend(["-ns", self.ad_config.dc_ip])
|
|
145
|
+
|
|
146
|
+
# Collection methods
|
|
147
|
+
if self.bh_config.collect_all:
|
|
148
|
+
cmd.extend(["-c", "all"])
|
|
149
|
+
else:
|
|
150
|
+
methods = []
|
|
151
|
+
if self.bh_config.collect_users:
|
|
152
|
+
methods.append("users")
|
|
153
|
+
if self.bh_config.collect_groups:
|
|
154
|
+
methods.append("groups")
|
|
155
|
+
if self.bh_config.collect_computers:
|
|
156
|
+
methods.append("computers")
|
|
157
|
+
if self.bh_config.collect_sessions:
|
|
158
|
+
methods.append("sessions")
|
|
159
|
+
if self.bh_config.collect_acls:
|
|
160
|
+
methods.append("acl")
|
|
161
|
+
if self.bh_config.collect_trusts:
|
|
162
|
+
methods.append("trusts")
|
|
163
|
+
if self.bh_config.collect_gpos:
|
|
164
|
+
methods.append("gpo")
|
|
165
|
+
|
|
166
|
+
if methods:
|
|
167
|
+
cmd.extend(["-c", ",".join(methods)])
|
|
168
|
+
|
|
169
|
+
# Output options
|
|
170
|
+
output_dir = Path(self.bh_config.output_dir)
|
|
171
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
172
|
+
cmd.extend(["--outputdir", str(output_dir)])
|
|
173
|
+
|
|
174
|
+
if self.bh_config.output_prefix:
|
|
175
|
+
cmd.extend(["--prefix", self.bh_config.output_prefix])
|
|
176
|
+
|
|
177
|
+
if self.bh_config.compress:
|
|
178
|
+
cmd.append("--zip")
|
|
179
|
+
|
|
180
|
+
# Performance options
|
|
181
|
+
cmd.extend(["--workers", str(self.bh_config.threads)])
|
|
182
|
+
|
|
183
|
+
if self.bh_config.dns_tcp:
|
|
184
|
+
cmd.append("--dns-tcp")
|
|
185
|
+
|
|
186
|
+
cmd.extend(["--dns-timeout", str(self.bh_config.dns_timeout)])
|
|
187
|
+
|
|
188
|
+
return cmd
|
|
189
|
+
|
|
190
|
+
async def collect(self, timeout: int = 3600) -> BloodHoundResult:
|
|
191
|
+
"""
|
|
192
|
+
Run BloodHound collection.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
timeout: Collection timeout in seconds
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
BloodHoundResult with collection results
|
|
199
|
+
"""
|
|
200
|
+
if not await self.check_installed():
|
|
201
|
+
raise RuntimeError(
|
|
202
|
+
"bloodhound-python is not installed. "
|
|
203
|
+
"Install with: pip install bloodhound"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
started_at = datetime.now(timezone.utc).isoformat()
|
|
207
|
+
start_time = asyncio.get_event_loop().time()
|
|
208
|
+
|
|
209
|
+
cmd = self._build_command()
|
|
210
|
+
print(f"[*] Running: {' '.join(cmd)}")
|
|
211
|
+
|
|
212
|
+
process = await asyncio.create_subprocess_exec(
|
|
213
|
+
*cmd,
|
|
214
|
+
stdout=asyncio.subprocess.PIPE,
|
|
215
|
+
stderr=asyncio.subprocess.PIPE
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
stdout, stderr = await asyncio.wait_for(
|
|
220
|
+
process.communicate(),
|
|
221
|
+
timeout=timeout
|
|
222
|
+
)
|
|
223
|
+
except asyncio.TimeoutError:
|
|
224
|
+
process.kill()
|
|
225
|
+
raise TimeoutError(f"BloodHound collection timed out after {timeout}s")
|
|
226
|
+
|
|
227
|
+
finished_at = datetime.now(timezone.utc).isoformat()
|
|
228
|
+
duration = asyncio.get_event_loop().time() - start_time
|
|
229
|
+
|
|
230
|
+
# Find output files
|
|
231
|
+
output_dir = Path(self.bh_config.output_dir)
|
|
232
|
+
output_files = []
|
|
233
|
+
|
|
234
|
+
# Look for JSON files
|
|
235
|
+
json_files = list(output_dir.glob("*.json"))
|
|
236
|
+
output_files.extend([str(f) for f in json_files])
|
|
237
|
+
|
|
238
|
+
# Look for ZIP files
|
|
239
|
+
zip_files = list(output_dir.glob("*.zip"))
|
|
240
|
+
output_files.extend([str(f) for f in zip_files])
|
|
241
|
+
|
|
242
|
+
self.output_files = output_files
|
|
243
|
+
|
|
244
|
+
# Parse statistics from output
|
|
245
|
+
statistics = self._parse_statistics(stdout.decode() + stderr.decode())
|
|
246
|
+
|
|
247
|
+
# Determine collection methods used
|
|
248
|
+
methods = []
|
|
249
|
+
if self.bh_config.collect_all:
|
|
250
|
+
methods = ["all"]
|
|
251
|
+
else:
|
|
252
|
+
if self.bh_config.collect_users:
|
|
253
|
+
methods.append("users")
|
|
254
|
+
if self.bh_config.collect_groups:
|
|
255
|
+
methods.append("groups")
|
|
256
|
+
if self.bh_config.collect_computers:
|
|
257
|
+
methods.append("computers")
|
|
258
|
+
if self.bh_config.collect_sessions:
|
|
259
|
+
methods.append("sessions")
|
|
260
|
+
if self.bh_config.collect_acls:
|
|
261
|
+
methods.append("acl")
|
|
262
|
+
if self.bh_config.collect_trusts:
|
|
263
|
+
methods.append("trusts")
|
|
264
|
+
|
|
265
|
+
status = "completed" if process.returncode == 0 else "failed"
|
|
266
|
+
|
|
267
|
+
return BloodHoundResult(
|
|
268
|
+
domain=self.ad_config.domain,
|
|
269
|
+
status=status,
|
|
270
|
+
started_at=started_at,
|
|
271
|
+
finished_at=finished_at,
|
|
272
|
+
duration=duration,
|
|
273
|
+
output_files=output_files,
|
|
274
|
+
collection_methods=methods,
|
|
275
|
+
statistics=statistics,
|
|
276
|
+
attack_paths=[], # Would need BloodHound DB analysis
|
|
277
|
+
metadata={
|
|
278
|
+
"return_code": process.returncode,
|
|
279
|
+
"output": stdout.decode()[:1000],
|
|
280
|
+
"errors": stderr.decode() if process.returncode != 0 else ""
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def _parse_statistics(self, output: str) -> Dict[str, int]:
|
|
285
|
+
"""Parse collection statistics from output."""
|
|
286
|
+
stats = {
|
|
287
|
+
"users": 0,
|
|
288
|
+
"groups": 0,
|
|
289
|
+
"computers": 0,
|
|
290
|
+
"sessions": 0,
|
|
291
|
+
"acls": 0,
|
|
292
|
+
"trusts": 0
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
import re
|
|
296
|
+
|
|
297
|
+
# Parse common output patterns
|
|
298
|
+
patterns = {
|
|
299
|
+
"users": r"(\d+)\s+users",
|
|
300
|
+
"groups": r"(\d+)\s+groups",
|
|
301
|
+
"computers": r"(\d+)\s+computers",
|
|
302
|
+
"sessions": r"(\d+)\s+sessions",
|
|
303
|
+
"acls": r"(\d+)\s+acls?",
|
|
304
|
+
"trusts": r"(\d+)\s+trusts?"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for stat_name, pattern in patterns.items():
|
|
308
|
+
match = re.search(pattern, output, re.IGNORECASE)
|
|
309
|
+
if match:
|
|
310
|
+
stats[stat_name] = int(match.group(1))
|
|
311
|
+
|
|
312
|
+
return stats
|
|
313
|
+
|
|
314
|
+
def parse_json_output(self) -> Dict[str, List[Dict]]:
|
|
315
|
+
"""
|
|
316
|
+
Parse collected JSON files.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Dict with parsed data by type
|
|
320
|
+
"""
|
|
321
|
+
data = {
|
|
322
|
+
"users": [],
|
|
323
|
+
"groups": [],
|
|
324
|
+
"computers": [],
|
|
325
|
+
"domains": [],
|
|
326
|
+
"gpos": [],
|
|
327
|
+
"ous": []
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for file_path in self.output_files:
|
|
331
|
+
if not file_path.endswith(".json"):
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
with open(file_path, "r") as f:
|
|
336
|
+
content = json.load(f)
|
|
337
|
+
|
|
338
|
+
# Determine type from filename or content
|
|
339
|
+
file_name = Path(file_path).name.lower()
|
|
340
|
+
|
|
341
|
+
if "users" in file_name:
|
|
342
|
+
data["users"].extend(content.get("data", []))
|
|
343
|
+
elif "groups" in file_name:
|
|
344
|
+
data["groups"].extend(content.get("data", []))
|
|
345
|
+
elif "computers" in file_name:
|
|
346
|
+
data["computers"].extend(content.get("data", []))
|
|
347
|
+
elif "domains" in file_name:
|
|
348
|
+
data["domains"].extend(content.get("data", []))
|
|
349
|
+
elif "gpos" in file_name:
|
|
350
|
+
data["gpos"].extend(content.get("data", []))
|
|
351
|
+
elif "ous" in file_name:
|
|
352
|
+
data["ous"].extend(content.get("data", []))
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
print(f"[!] Error parsing {file_path}: {e}")
|
|
356
|
+
|
|
357
|
+
return data
|
|
358
|
+
|
|
359
|
+
def identify_high_value_targets(self) -> List[Dict]:
|
|
360
|
+
"""
|
|
361
|
+
Identify high-value targets from collected data.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
List of high-value targets
|
|
365
|
+
"""
|
|
366
|
+
targets = []
|
|
367
|
+
data = self.parse_json_output()
|
|
368
|
+
|
|
369
|
+
# Look for Domain Admins members
|
|
370
|
+
for group in data.get("groups", []):
|
|
371
|
+
props = group.get("Properties", {})
|
|
372
|
+
name = props.get("name", "")
|
|
373
|
+
|
|
374
|
+
if "DOMAIN ADMINS" in name.upper():
|
|
375
|
+
members = group.get("Members", [])
|
|
376
|
+
for member in members:
|
|
377
|
+
targets.append({
|
|
378
|
+
"type": "user",
|
|
379
|
+
"name": member.get("MemberId", ""),
|
|
380
|
+
"reason": "Domain Admin member",
|
|
381
|
+
"priority": "critical"
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
# Look for computers with unconstrained delegation
|
|
385
|
+
for computer in data.get("computers", []):
|
|
386
|
+
props = computer.get("Properties", {})
|
|
387
|
+
if props.get("unconstraineddelegation", False):
|
|
388
|
+
targets.append({
|
|
389
|
+
"type": "computer",
|
|
390
|
+
"name": props.get("name", ""),
|
|
391
|
+
"reason": "Unconstrained delegation",
|
|
392
|
+
"priority": "high"
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
# Look for users with admincount
|
|
396
|
+
for user in data.get("users", []):
|
|
397
|
+
props = user.get("Properties", {})
|
|
398
|
+
if props.get("admincount", False):
|
|
399
|
+
targets.append({
|
|
400
|
+
"type": "user",
|
|
401
|
+
"name": props.get("name", ""),
|
|
402
|
+
"reason": "Admin count set",
|
|
403
|
+
"priority": "medium"
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
return targets
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# Convenience function
|
|
410
|
+
async def run_bloodhound(
|
|
411
|
+
domain: str,
|
|
412
|
+
dc_ip: str,
|
|
413
|
+
username: str,
|
|
414
|
+
password: str,
|
|
415
|
+
output_dir: str = "./bloodhound_output",
|
|
416
|
+
collect_all: bool = True,
|
|
417
|
+
**kwargs
|
|
418
|
+
) -> BloodHoundResult:
|
|
419
|
+
"""
|
|
420
|
+
Quick BloodHound collection.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
domain: AD domain
|
|
424
|
+
dc_ip: Domain Controller IP
|
|
425
|
+
username: Username
|
|
426
|
+
password: Password
|
|
427
|
+
output_dir: Output directory
|
|
428
|
+
collect_all: Collect all data
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
BloodHoundResult
|
|
432
|
+
"""
|
|
433
|
+
ad_config = get_ad_config(
|
|
434
|
+
domain=domain,
|
|
435
|
+
dc_ip=dc_ip,
|
|
436
|
+
username=username,
|
|
437
|
+
password=password
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
bh_config = BloodHoundConfig(
|
|
441
|
+
collect_all=collect_all,
|
|
442
|
+
output_dir=output_dir,
|
|
443
|
+
compress=True
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
wrapper = BloodHoundWrapper(ad_config, bh_config)
|
|
447
|
+
return await wrapper.collect()
|