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,533 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LDAP Enumeration for Active Directory
|
|
3
|
+
|
|
4
|
+
Comprehensive LDAP enumeration including:
|
|
5
|
+
- User enumeration
|
|
6
|
+
- Group enumeration
|
|
7
|
+
- Computer enumeration
|
|
8
|
+
- GPO enumeration
|
|
9
|
+
- Domain trust enumeration
|
|
10
|
+
- Service account detection
|
|
11
|
+
- Privileged account identification
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from aipt_v2.tools.active_directory import LDAPEnum
|
|
15
|
+
|
|
16
|
+
enum = LDAPEnum(config)
|
|
17
|
+
users = await enum.enumerate_users()
|
|
18
|
+
groups = await enum.enumerate_groups()
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from datetime import datetime, timezone
|
|
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
|
+
try:
|
|
29
|
+
import ldap3
|
|
30
|
+
from ldap3 import Server, Connection, ALL, NTLM, KERBEROS, SASL
|
|
31
|
+
HAS_LDAP3 = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
HAS_LDAP3 = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class LDAPFinding:
|
|
38
|
+
"""LDAP enumeration finding."""
|
|
39
|
+
category: str # user, group, computer, gpo, trust, misc
|
|
40
|
+
severity: str # critical, high, medium, low, info
|
|
41
|
+
title: str
|
|
42
|
+
description: str
|
|
43
|
+
object_dn: str
|
|
44
|
+
attributes: Dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
remediation: str = ""
|
|
46
|
+
timestamp: str = ""
|
|
47
|
+
|
|
48
|
+
def __post_init__(self):
|
|
49
|
+
if not self.timestamp:
|
|
50
|
+
self.timestamp = datetime.now(timezone.utc).isoformat()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class LDAPEnumResult:
|
|
55
|
+
"""Result of LDAP enumeration."""
|
|
56
|
+
domain: str
|
|
57
|
+
status: str
|
|
58
|
+
started_at: str
|
|
59
|
+
finished_at: str
|
|
60
|
+
duration: float
|
|
61
|
+
users: List[Dict]
|
|
62
|
+
groups: List[Dict]
|
|
63
|
+
computers: List[Dict]
|
|
64
|
+
gpos: List[Dict]
|
|
65
|
+
trusts: List[Dict]
|
|
66
|
+
findings: List[LDAPFinding]
|
|
67
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LDAPEnum:
|
|
71
|
+
"""
|
|
72
|
+
LDAP Enumeration Tool.
|
|
73
|
+
|
|
74
|
+
Enumerates Active Directory objects via LDAP
|
|
75
|
+
and identifies security issues.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Privileged groups to flag
|
|
79
|
+
PRIVILEGED_GROUPS = [
|
|
80
|
+
"Domain Admins",
|
|
81
|
+
"Enterprise Admins",
|
|
82
|
+
"Schema Admins",
|
|
83
|
+
"Administrators",
|
|
84
|
+
"Account Operators",
|
|
85
|
+
"Backup Operators",
|
|
86
|
+
"Server Operators",
|
|
87
|
+
"Print Operators",
|
|
88
|
+
"DnsAdmins",
|
|
89
|
+
"Domain Controllers",
|
|
90
|
+
"Group Policy Creator Owners"
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Dangerous user attributes
|
|
94
|
+
DANGEROUS_FLAGS = {
|
|
95
|
+
0x00010000: "PASSWORD_NEVER_EXPIRES",
|
|
96
|
+
0x00020000: "MNS_LOGON_ACCOUNT",
|
|
97
|
+
0x00000002: "ACCOUNT_DISABLED",
|
|
98
|
+
0x00000010: "LOCKOUT",
|
|
99
|
+
0x00000020: "PASSWORD_NOT_REQUIRED",
|
|
100
|
+
0x00000040: "PASSWORD_CANT_CHANGE",
|
|
101
|
+
0x00000080: "ENCRYPTED_TEXT_PASSWORD_ALLOWED",
|
|
102
|
+
0x00200000: "TRUSTED_FOR_DELEGATION",
|
|
103
|
+
0x00400000: "NOT_DELEGATED",
|
|
104
|
+
0x00800000: "USE_DES_KEY_ONLY",
|
|
105
|
+
0x01000000: "DONT_REQ_PREAUTH",
|
|
106
|
+
0x04000000: "TRUSTED_TO_AUTH_FOR_DELEGATION"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def __init__(self, config: Optional[ADConfig] = None):
|
|
110
|
+
"""
|
|
111
|
+
Initialize LDAP enumerator.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
config: AD configuration
|
|
115
|
+
"""
|
|
116
|
+
if not HAS_LDAP3:
|
|
117
|
+
raise ImportError("ldap3 is required. Install with: pip install ldap3")
|
|
118
|
+
|
|
119
|
+
self.config = config or ADConfig()
|
|
120
|
+
self.connection: Optional[Connection] = None
|
|
121
|
+
self.findings: List[LDAPFinding] = []
|
|
122
|
+
|
|
123
|
+
def _connect(self) -> bool:
|
|
124
|
+
"""Establish LDAP connection."""
|
|
125
|
+
try:
|
|
126
|
+
server = Server(
|
|
127
|
+
self.config.dc_ip,
|
|
128
|
+
port=self.config.ldaps_port if self.config.use_ssl else self.config.ldap_port,
|
|
129
|
+
use_ssl=self.config.use_ssl,
|
|
130
|
+
get_info=ALL
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Determine authentication method
|
|
134
|
+
if self.config.credentials.use_kerberos:
|
|
135
|
+
self.connection = Connection(
|
|
136
|
+
server,
|
|
137
|
+
authentication=SASL,
|
|
138
|
+
sasl_mechanism=KERBEROS
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
# NTLM authentication
|
|
142
|
+
user = self.config.credentials.get_auth_string()
|
|
143
|
+
password = self.config.credentials.get_password_or_hash()
|
|
144
|
+
|
|
145
|
+
self.connection = Connection(
|
|
146
|
+
server,
|
|
147
|
+
user=user,
|
|
148
|
+
password=password,
|
|
149
|
+
authentication=NTLM
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return self.connection.bind()
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f"[!] LDAP connection failed: {e}")
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def _search(
|
|
159
|
+
self,
|
|
160
|
+
search_filter: str,
|
|
161
|
+
attributes: List[str] = None,
|
|
162
|
+
search_base: str = None
|
|
163
|
+
) -> List[Dict]:
|
|
164
|
+
"""Execute LDAP search."""
|
|
165
|
+
if not self.connection:
|
|
166
|
+
return []
|
|
167
|
+
|
|
168
|
+
base = search_base or self.config.get_base_dn()
|
|
169
|
+
attrs = attributes or ["*"]
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
self.connection.search(
|
|
173
|
+
search_base=base,
|
|
174
|
+
search_filter=search_filter,
|
|
175
|
+
attributes=attrs
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
results = []
|
|
179
|
+
for entry in self.connection.entries:
|
|
180
|
+
entry_dict = {
|
|
181
|
+
"dn": entry.entry_dn,
|
|
182
|
+
"attributes": dict(entry.entry_attributes_as_dict)
|
|
183
|
+
}
|
|
184
|
+
results.append(entry_dict)
|
|
185
|
+
|
|
186
|
+
return results
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
print(f"[!] LDAP search failed: {e}")
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
def enumerate_users(self) -> List[Dict]:
|
|
193
|
+
"""Enumerate domain users."""
|
|
194
|
+
users = self._search(
|
|
195
|
+
"(objectClass=user)",
|
|
196
|
+
attributes=[
|
|
197
|
+
"sAMAccountName", "userPrincipalName", "displayName",
|
|
198
|
+
"description", "memberOf", "userAccountControl",
|
|
199
|
+
"pwdLastSet", "lastLogonTimestamp", "adminCount",
|
|
200
|
+
"servicePrincipalName", "msDS-AllowedToDelegateTo"
|
|
201
|
+
]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Analyze users for security issues
|
|
205
|
+
for user in users:
|
|
206
|
+
attrs = user.get("attributes", {})
|
|
207
|
+
sam = attrs.get("sAMAccountName", [""])[0]
|
|
208
|
+
uac = attrs.get("userAccountControl", [0])[0]
|
|
209
|
+
|
|
210
|
+
# Check for dangerous flags
|
|
211
|
+
if isinstance(uac, int):
|
|
212
|
+
for flag_value, flag_name in self.DANGEROUS_FLAGS.items():
|
|
213
|
+
if uac & flag_value:
|
|
214
|
+
severity = "high" if flag_name in [
|
|
215
|
+
"DONT_REQ_PREAUTH", "TRUSTED_FOR_DELEGATION",
|
|
216
|
+
"PASSWORD_NOT_REQUIRED"
|
|
217
|
+
] else "medium"
|
|
218
|
+
|
|
219
|
+
self.findings.append(LDAPFinding(
|
|
220
|
+
category="user",
|
|
221
|
+
severity=severity,
|
|
222
|
+
title=f"User has {flag_name} flag",
|
|
223
|
+
description=f"User {sam} has {flag_name} UAC flag set",
|
|
224
|
+
object_dn=user.get("dn", ""),
|
|
225
|
+
attributes={"flag": flag_name, "uac": uac},
|
|
226
|
+
remediation=f"Review and remove {flag_name} flag if not required"
|
|
227
|
+
))
|
|
228
|
+
|
|
229
|
+
# Check for service accounts (Kerberoastable)
|
|
230
|
+
spns = attrs.get("servicePrincipalName", [])
|
|
231
|
+
if spns:
|
|
232
|
+
self.findings.append(LDAPFinding(
|
|
233
|
+
category="user",
|
|
234
|
+
severity="high",
|
|
235
|
+
title="Kerberoastable Account",
|
|
236
|
+
description=f"User {sam} has SPNs and may be Kerberoastable",
|
|
237
|
+
object_dn=user.get("dn", ""),
|
|
238
|
+
attributes={"spns": spns},
|
|
239
|
+
remediation="Use strong passwords for service accounts"
|
|
240
|
+
))
|
|
241
|
+
|
|
242
|
+
# Check for delegation
|
|
243
|
+
delegation = attrs.get("msDS-AllowedToDelegateTo", [])
|
|
244
|
+
if delegation:
|
|
245
|
+
self.findings.append(LDAPFinding(
|
|
246
|
+
category="user",
|
|
247
|
+
severity="high",
|
|
248
|
+
title="Constrained Delegation",
|
|
249
|
+
description=f"User {sam} can delegate to specific services",
|
|
250
|
+
object_dn=user.get("dn", ""),
|
|
251
|
+
attributes={"delegation_targets": delegation},
|
|
252
|
+
remediation="Review delegation configuration"
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
# Check admin count
|
|
256
|
+
if attrs.get("adminCount", [0])[0] == 1:
|
|
257
|
+
self.findings.append(LDAPFinding(
|
|
258
|
+
category="user",
|
|
259
|
+
severity="info",
|
|
260
|
+
title="Protected Admin Account",
|
|
261
|
+
description=f"User {sam} has adminCount=1 (was/is admin)",
|
|
262
|
+
object_dn=user.get("dn", ""),
|
|
263
|
+
attributes={},
|
|
264
|
+
remediation="Verify admin privileges are appropriate"
|
|
265
|
+
))
|
|
266
|
+
|
|
267
|
+
return users
|
|
268
|
+
|
|
269
|
+
def enumerate_groups(self) -> List[Dict]:
|
|
270
|
+
"""Enumerate domain groups."""
|
|
271
|
+
groups = self._search(
|
|
272
|
+
"(objectClass=group)",
|
|
273
|
+
attributes=[
|
|
274
|
+
"sAMAccountName", "description", "member",
|
|
275
|
+
"memberOf", "adminCount", "groupType"
|
|
276
|
+
]
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Check for privileged groups
|
|
280
|
+
for group in groups:
|
|
281
|
+
attrs = group.get("attributes", {})
|
|
282
|
+
name = attrs.get("sAMAccountName", [""])[0]
|
|
283
|
+
members = attrs.get("member", [])
|
|
284
|
+
|
|
285
|
+
if name in self.PRIVILEGED_GROUPS:
|
|
286
|
+
self.findings.append(LDAPFinding(
|
|
287
|
+
category="group",
|
|
288
|
+
severity="info",
|
|
289
|
+
title=f"Privileged Group: {name}",
|
|
290
|
+
description=f"Group {name} has {len(members)} members",
|
|
291
|
+
object_dn=group.get("dn", ""),
|
|
292
|
+
attributes={"member_count": len(members)},
|
|
293
|
+
remediation="Review membership regularly"
|
|
294
|
+
))
|
|
295
|
+
|
|
296
|
+
return groups
|
|
297
|
+
|
|
298
|
+
def enumerate_computers(self) -> List[Dict]:
|
|
299
|
+
"""Enumerate domain computers."""
|
|
300
|
+
computers = self._search(
|
|
301
|
+
"(objectClass=computer)",
|
|
302
|
+
attributes=[
|
|
303
|
+
"sAMAccountName", "dNSHostName", "operatingSystem",
|
|
304
|
+
"operatingSystemVersion", "userAccountControl",
|
|
305
|
+
"lastLogonTimestamp", "servicePrincipalName",
|
|
306
|
+
"msDS-AllowedToDelegateTo"
|
|
307
|
+
]
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
for computer in computers:
|
|
311
|
+
attrs = computer.get("attributes", {})
|
|
312
|
+
name = attrs.get("sAMAccountName", [""])[0]
|
|
313
|
+
os_name = attrs.get("operatingSystem", [""])[0]
|
|
314
|
+
|
|
315
|
+
# Check for legacy OS
|
|
316
|
+
if os_name and any(legacy in os_name.lower() for legacy in [
|
|
317
|
+
"2003", "2008", "xp", "vista", "windows 7"
|
|
318
|
+
]):
|
|
319
|
+
self.findings.append(LDAPFinding(
|
|
320
|
+
category="computer",
|
|
321
|
+
severity="high",
|
|
322
|
+
title="Legacy Operating System",
|
|
323
|
+
description=f"Computer {name} runs {os_name}",
|
|
324
|
+
object_dn=computer.get("dn", ""),
|
|
325
|
+
attributes={"os": os_name},
|
|
326
|
+
remediation="Upgrade to supported operating system"
|
|
327
|
+
))
|
|
328
|
+
|
|
329
|
+
# Check for unconstrained delegation
|
|
330
|
+
uac = attrs.get("userAccountControl", [0])[0]
|
|
331
|
+
if isinstance(uac, int) and (uac & 0x00080000): # TRUSTED_FOR_DELEGATION
|
|
332
|
+
self.findings.append(LDAPFinding(
|
|
333
|
+
category="computer",
|
|
334
|
+
severity="critical",
|
|
335
|
+
title="Unconstrained Delegation",
|
|
336
|
+
description=f"Computer {name} has unconstrained delegation",
|
|
337
|
+
object_dn=computer.get("dn", ""),
|
|
338
|
+
attributes={},
|
|
339
|
+
remediation="Remove unconstrained delegation or use constrained"
|
|
340
|
+
))
|
|
341
|
+
|
|
342
|
+
return computers
|
|
343
|
+
|
|
344
|
+
def enumerate_gpos(self) -> List[Dict]:
|
|
345
|
+
"""Enumerate Group Policy Objects."""
|
|
346
|
+
gpos = self._search(
|
|
347
|
+
"(objectClass=groupPolicyContainer)",
|
|
348
|
+
attributes=[
|
|
349
|
+
"displayName", "gPCFileSysPath", "versionNumber",
|
|
350
|
+
"gPCFunctionalityVersion"
|
|
351
|
+
]
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return gpos
|
|
355
|
+
|
|
356
|
+
def enumerate_trusts(self) -> List[Dict]:
|
|
357
|
+
"""Enumerate domain trusts."""
|
|
358
|
+
trusts = self._search(
|
|
359
|
+
"(objectClass=trustedDomain)",
|
|
360
|
+
attributes=[
|
|
361
|
+
"name", "trustDirection", "trustType",
|
|
362
|
+
"trustAttributes", "securityIdentifier"
|
|
363
|
+
]
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
for trust in trusts:
|
|
367
|
+
attrs = trust.get("attributes", {})
|
|
368
|
+
name = attrs.get("name", [""])[0]
|
|
369
|
+
direction = attrs.get("trustDirection", [0])[0]
|
|
370
|
+
|
|
371
|
+
direction_map = {
|
|
372
|
+
0: "Disabled",
|
|
373
|
+
1: "Inbound",
|
|
374
|
+
2: "Outbound",
|
|
375
|
+
3: "Bidirectional"
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
self.findings.append(LDAPFinding(
|
|
379
|
+
category="trust",
|
|
380
|
+
severity="info",
|
|
381
|
+
title=f"Domain Trust: {name}",
|
|
382
|
+
description=f"Trust direction: {direction_map.get(direction, 'Unknown')}",
|
|
383
|
+
object_dn=trust.get("dn", ""),
|
|
384
|
+
attributes={"direction": direction_map.get(direction)},
|
|
385
|
+
remediation="Review trust relationships regularly"
|
|
386
|
+
))
|
|
387
|
+
|
|
388
|
+
return trusts
|
|
389
|
+
|
|
390
|
+
def enumerate_asreproastable(self) -> List[Dict]:
|
|
391
|
+
"""Find users vulnerable to AS-REP roasting."""
|
|
392
|
+
# Users with DONT_REQ_PREAUTH flag
|
|
393
|
+
users = self._search(
|
|
394
|
+
"(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))",
|
|
395
|
+
attributes=["sAMAccountName", "userPrincipalName", "description"]
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
for user in users:
|
|
399
|
+
attrs = user.get("attributes", {})
|
|
400
|
+
sam = attrs.get("sAMAccountName", [""])[0]
|
|
401
|
+
|
|
402
|
+
self.findings.append(LDAPFinding(
|
|
403
|
+
category="user",
|
|
404
|
+
severity="high",
|
|
405
|
+
title="AS-REP Roastable Account",
|
|
406
|
+
description=f"User {sam} does not require Kerberos pre-authentication",
|
|
407
|
+
object_dn=user.get("dn", ""),
|
|
408
|
+
attributes={},
|
|
409
|
+
remediation="Enable Kerberos pre-authentication"
|
|
410
|
+
))
|
|
411
|
+
|
|
412
|
+
return users
|
|
413
|
+
|
|
414
|
+
def enumerate_laps(self) -> List[Dict]:
|
|
415
|
+
"""Check for LAPS deployment."""
|
|
416
|
+
# Check if LAPS schema is present
|
|
417
|
+
computers = self._search(
|
|
418
|
+
"(&(objectClass=computer)(ms-Mcs-AdmPwd=*))",
|
|
419
|
+
attributes=["sAMAccountName", "ms-Mcs-AdmPwd", "ms-Mcs-AdmPwdExpirationTime"]
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if computers:
|
|
423
|
+
self.findings.append(LDAPFinding(
|
|
424
|
+
category="misc",
|
|
425
|
+
severity="info",
|
|
426
|
+
title="LAPS Deployed",
|
|
427
|
+
description=f"LAPS is deployed on {len(computers)} computers",
|
|
428
|
+
object_dn="",
|
|
429
|
+
attributes={"computer_count": len(computers)},
|
|
430
|
+
remediation="Ensure LAPS passwords are retrieved securely"
|
|
431
|
+
))
|
|
432
|
+
else:
|
|
433
|
+
self.findings.append(LDAPFinding(
|
|
434
|
+
category="misc",
|
|
435
|
+
severity="medium",
|
|
436
|
+
title="LAPS Not Detected",
|
|
437
|
+
description="LAPS does not appear to be deployed",
|
|
438
|
+
object_dn="",
|
|
439
|
+
attributes={},
|
|
440
|
+
remediation="Consider deploying LAPS for local admin password management"
|
|
441
|
+
))
|
|
442
|
+
|
|
443
|
+
return computers
|
|
444
|
+
|
|
445
|
+
async def enumerate_all(self) -> LDAPEnumResult:
|
|
446
|
+
"""Run full LDAP enumeration."""
|
|
447
|
+
started_at = datetime.now(timezone.utc).isoformat()
|
|
448
|
+
start_time = asyncio.get_event_loop().time()
|
|
449
|
+
|
|
450
|
+
if not self._connect():
|
|
451
|
+
return LDAPEnumResult(
|
|
452
|
+
domain=self.config.domain,
|
|
453
|
+
status="failed",
|
|
454
|
+
started_at=started_at,
|
|
455
|
+
finished_at=datetime.now(timezone.utc).isoformat(),
|
|
456
|
+
duration=0,
|
|
457
|
+
users=[],
|
|
458
|
+
groups=[],
|
|
459
|
+
computers=[],
|
|
460
|
+
gpos=[],
|
|
461
|
+
trusts=[],
|
|
462
|
+
findings=[],
|
|
463
|
+
metadata={"error": "LDAP connection failed"}
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Run enumeration
|
|
467
|
+
users = self.enumerate_users()
|
|
468
|
+
groups = self.enumerate_groups()
|
|
469
|
+
computers = self.enumerate_computers()
|
|
470
|
+
gpos = self.enumerate_gpos()
|
|
471
|
+
trusts = self.enumerate_trusts()
|
|
472
|
+
self.enumerate_asreproastable()
|
|
473
|
+
self.enumerate_laps()
|
|
474
|
+
|
|
475
|
+
# Close connection
|
|
476
|
+
if self.connection:
|
|
477
|
+
self.connection.unbind()
|
|
478
|
+
|
|
479
|
+
finished_at = datetime.now(timezone.utc).isoformat()
|
|
480
|
+
duration = asyncio.get_event_loop().time() - start_time
|
|
481
|
+
|
|
482
|
+
return LDAPEnumResult(
|
|
483
|
+
domain=self.config.domain,
|
|
484
|
+
status="completed",
|
|
485
|
+
started_at=started_at,
|
|
486
|
+
finished_at=finished_at,
|
|
487
|
+
duration=duration,
|
|
488
|
+
users=users,
|
|
489
|
+
groups=groups,
|
|
490
|
+
computers=computers,
|
|
491
|
+
gpos=gpos,
|
|
492
|
+
trusts=trusts,
|
|
493
|
+
findings=self.findings,
|
|
494
|
+
metadata={
|
|
495
|
+
"user_count": len(users),
|
|
496
|
+
"group_count": len(groups),
|
|
497
|
+
"computer_count": len(computers),
|
|
498
|
+
"finding_count": len(self.findings)
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
# Convenience function
|
|
504
|
+
async def enumerate_ldap(
|
|
505
|
+
domain: str,
|
|
506
|
+
dc_ip: str,
|
|
507
|
+
username: str,
|
|
508
|
+
password: str,
|
|
509
|
+
**kwargs
|
|
510
|
+
) -> LDAPEnumResult:
|
|
511
|
+
"""
|
|
512
|
+
Quick LDAP enumeration.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
domain: AD domain
|
|
516
|
+
dc_ip: Domain Controller IP
|
|
517
|
+
username: Username
|
|
518
|
+
password: Password
|
|
519
|
+
**kwargs: Additional options
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
LDAPEnumResult
|
|
523
|
+
"""
|
|
524
|
+
config = get_ad_config(
|
|
525
|
+
domain=domain,
|
|
526
|
+
dc_ip=dc_ip,
|
|
527
|
+
username=username,
|
|
528
|
+
password=password,
|
|
529
|
+
**kwargs
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
enum = LDAPEnum(config)
|
|
533
|
+
return await enum.enumerate_all()
|