Attacking LDAP: Deep Dive & Cheatsheet

Oct 16, 2024    #active-directory   #windows   #ldap   #cheatsheet   #pentesting  

LDAP Overview:

LDAP Requests & Responses (how a session works):

LDAP AD Authentication:

_

OpenLDAP:

LDAP signing:

LDAP Bind Request:

A bind request consists of 3 elements:

  1. The LDAP protocol the clients wants to use:
    • This is represented by an integer value.
  2. The DN of the client/user to authentication:
    • For an Anonymous Bind it would be empty. It would also typically be empty for SASL Authentication as SASL uses encoded credentials
  3. The credentials the client/user uses to authentication:
    • For simple authentication this is the password for the DN specified in part 2. For Anonymous bind the string would be empty, for SASL authentication this is an encoded value.

LDAP Anonymous Bind:

LDAP Filters:

LDAP Filter Operands:

ldapsearch -H ldap://10.129.204.54 -x -b "DC=sugarape,DC=local" '(&(ObjectClass=user)(cn=nathan*))' logoncount
ldapsearch -H ldap://10.129.204.54 -x -b "DC=sugarape,DC=local" '(&(ObjectClass=user)(cn=nathan*))' sAMAccountName
ldapsearch -H ldap://10.129.204.54 -x -b "DC=sugarape,DC=local" '(&(ObjectClass=user)(cn=nathan*))'

LDAP Logical Operators:

Operator Meaning Description
= Equality Operator Checks for exact match between attribute and value.
~= Approximately equal to Finds entries where the attribute is approximately equal to the given value.
>= Greater than or equal to Finds entries where the attribute value is greater than or equal to the given value.
<= Less than or equal to Finds entries where the attribute value is less than or equal to the given value.
=* Presence Test Checks if an attribute is present, regardless of its value.

+Examples of Operators in use+:

Operator Rule Example
Equal to (attribute=123) (&(objectclass=user)(displayName=Smith))
Not equal to (!(attribute=123)) (!(objectClass=group))
Present (attribute=*) (department=*)
Not present (!(attribute=*)) (!homeDirectory=*)
Greater than (attribute>=123) (maxStorage>=100000)
Less than (attribute<=123) (maxStorage<=100000)
Approximate match (attribute~=123) (sAMAccountName~=Jason)
Wildcards (attribute=*A) (givenName=*Sam)

LDAP Filter Item Types:

Type Meaning
= Simple
=* Present
=something* Substring
Extensible varies depending on type

LDAP Filter Escaped Characters:

Character Represented as Hex
* \2a
( \28
) \29
\ \5c
NUL \00

Object Identifiers OID’s:

Overview:

Structure:

Example of the tree structure:

Usage Example:

Breaking down the LDAP query: userAccountControl:1.2.840.113556.1.4.803:=8192

  1. userAccountControl: The attribute being queried
  2. 1.2.840.113556.1.4.803: OID for a specific matching rule (bitwise AND)
  3. :=: Equality operator in LDAP
  4. 8192: Decimal value for the specific flag being queried (SERVER_TRUST_ACCOUNT)

This query structure allows for precise and standardized searches within LDAP directories.

See LDAP Filter Using Object Identifiers OID’s:

Additional Resources:

LDAP Filter Using Object Identifiers :

LDAP Query/Queries:

+LDAP Search Terms+

List Domain Functionality Level :

ldapsearch -x -H ldap://[[DCIP]] -b "" -s base "objectClass=*" domainFunctionality""
ldapsearch -x -H ldap://10.129.42.188 -b "" -s base "objectClass=*" domainFunctionality""

List User Information:

List All User Information:

#Ldap Query
'(objectClass=user)' or '(&(objectCategory=person))'
# Examples Using ldapsearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(objectClass=user)'
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(&(objectCategory=person))'
#Ldap Query
Get-ADObject -LDAPFilter '(&(objectCategory=person))' or '(objectClass=user)'

# Examples Using LDAPFilter
Get-ADObject -LDAPFilter '(&(objectCategory=person))'
Get-ADObject -LDAPFilter '(&(objectCategory=person))' | select name | Measure-Object

List a specific Users Information:

#Ldap Query
'(&(ObjectClass=user)(cn=<name*>))'

# Examples Using ldapsearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(&(ObjectClass=user)(cn=nathan*))'
#Ldap Query
Get-ADObject -LDAPFilter '(&(ObjectClass=user)(cn=<name*>))'

# Examples Using LDAPFilter
Get-ADObject -LDAPFilter '(&(objectCategory=user)(cn=carol*))'

List Users Who have Constrained Delegation Privileges:

#Ldap Query
(userAccountControl:1.2.840.113556.1.4.803:=524288)

# Examples Using LDAPSearch
ldapsearch -D "cn=sugarape-student,dc=sugarape,DC=LOCAL" -w 'Academy_student_AD!' -H ldap://10.129.2.174 '(userAccountControl:1.2.840.113556.1.4.803:=524288)
#Ldap Query
(userAccountControl:1.2.840.113556.1.4.803:=524288)

# Example Using LDAPFilter
Get-DomainUser -LDAPFilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)"

Users with Administrative Privileges:

#Ldap Query
(&(objectClass=user)(adminCount=1))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(&(objectClass=user)(adminCount=1))'
# Example Using LDAPFilter
Get-ADObject -LDAPFilter '(&(objectCategory=user)(adminCount=1))'

List All administratively disabled accounts.:

#LDAP Query
(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))'
Get-ADObject -LDAPFilter '(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))'

List User Emails:

#LDAP Query
(&(objectClass=user)(mail=*@domain.com))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 (&(objectClass=user)(mail=*@sugarape.local))
#LDAP Query
(&(objectClass=user)(mail=*@domain.com))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter  (&(objectClass=user)(mail=*@sugarape.local))

List Group Information:

List All Groups:

#LDAP Query
(objectClass=group)

# Example Using LDAPSearch
ldapsearch -H ldap://monteverde.MEGABANK.LOCAL -x -b "DC=MEGABANK,DC=LOCAL" -s sub "(&(objectclass=group))" | grep sAMAccountName: | cut -f2 -d" "
#LDAP Query
(&(objectCategory=group))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter '(&(objectCategory=group))' | select name | Measure-Object

List Group Membership of a specific user:

#LDAP Query
(&(objectClass=group)(member=CN=John Doe,CN=Users,DC=domain,DC=com))

# Example Using LDAPSearch
ldapsearch -x -b "dc=domain,dc=com" -H ldap://10.129.95.210 '(&(objectClass=group)(member=CN=John Doe,CN=Users,DC=domain,DC=com))'
#LDAP Query
(&(objectClass=group)(member=CN=John Doe,CN=Users,DC=domain,DC=com)

# Example Using LDAPFilter
Get-ADObject -LDAPFilter '(&(objectClass=group)(member=CN=John Doe,CN=Users,DC=domain,DC=com)'

List Members of a Specific Group:

#LDAP Query
(&(objectCategory=Person)(sAMAccountName=*)(memberOf=CN=<GroupName>,OU=Groups,DC=[DCNAME],DC=[DCNAME]))

# Example Using LDAPSearch
ldapsearch -H ldap://monteverde.MEGABANK.LOCAL -x -b "DC=MEGABANK,DC=LOCAL" -s sub "(&(objectCategory=Person)(sAMAccountName=*)(memberOf=CN=Helpdesk,OU=Groups,DC=MEGABANK,DC=LOCAL))"
#LDAP Query
(&(objectCategory=Person)(sAMAccountName=*)(memberOf=CN=<GroupName>,OU=Groups,DC=[DCNAME],DC=[DCNAME]))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(&(objectCategory=Person)(sAMAccountName=*)(memberOf=CN=Helpdesk,OU=Groups,DC=MEGABANK,DC=LOCAL))"

List Computers:

#LDAP Query
(objectClass=computer)

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(objectClass=computer)'
#LDAP Query
(objectClass=computer)

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(objectClass=computer)"

List OU information:

List All OU’s:

#LDAP Query
(objectClass=OrganizationalUnit)

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(objectClass=OrganizationalUnit)'
#LDAP Query
(objectClass=OrganizationalUnit)

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(objectClass=OrganizationalUnit)"

List Specific OU Information:

#LDAP Query
(ou=[OUName])

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 '(ou=Accounting)'
#LDAP Query
(ou=[OUName])

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(ou=Accounting)"

List Account Information:

List Active Accounts:

#LDAP Query
(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
ldapsearch -h 172.16.5.5 -x -b "DC=SUGARAPE,DC=LOCAL" -s sub "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" | grep "sugarape.local"
#LDAP Query
(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter  "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"

Account Expires in Specific Time Frame:

#LDAP Query
(&(objectClass=user)(accountExpires>=131342487000000000)(accountExpires<=131395327000000000))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 "(&(objectClass=user)(accountExpires>=131342487000000000)(accountExpires<=131395327000000000))"
#LDAP Query
(&(objectClass=user)(accountExpires>=131342487000000000)(accountExpires<=131395327000000000))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(&(objectClass=user)(accountExpires>=131342487000000000)(accountExpires<=131395327000000000))"

List Disabled Accounts:

#LDAP Query
(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))"
#LDAP Query

(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))"

Linux System Support:

#LDAP Query
(&(objectClass=device)(osName=Linux*))

# Example Using LDAPSearch
ldapsearch -x -b "dc=sugarape,dc=local" -H ldap://10.129.95.210 "(&(objectClass=device)(osName=Linux*))"
#LDAP Query
(&(objectClass=device)(osName=Linux*))

# Example Using LDAPFilter
Get-ADObject -LDAPFilter "(&(objectClass=device)(osName=Linux*))"

SearchBase and SearchScope Parameters:

The SearchBase parameter:

The SearchScope parameter:

Get-ADUser -SearchBase "OU=Employees,DC=CONTOSO,DC=LOCAL" -SearchScope OneLevel -Filter *
Get-ADUser -SearchBase "OU=IT,DC=CONTOSO,DC=LOCAL" -SearchScope 1 -Filter *
Get-ADUser -SearchBase "OU=Domain Admins,DC=CONTOSO,DC=LOCAL" -SearchScope 2 -Filter *
(Get-ADUser -SearchBase "OU=IT,DC=CONTOSO,DC=LOCAL" -SearchScope 2 -Filter *).count

+Enumerating LDAP+

LDAPire: Custom LDAP Enumeration Tool

LDAPire is my own custom-built Python-based tool for Active Directory reconnaissance and enumeration. It’s designed to streamline the process of gathering essential AD information during penetration tests or security assessments.

Key features:

Usage:

python3 pythonldap.py <DC_IP> [-u USERNAME] [-p PASSWORD]
#Example
python3 pythonldap.py 192.168.1.100 -u "DOMAIN\username"
python3 pythonldap.py 192.168.1.100 "FQDN/IP"

Output files:

For more information and to access the tool, visit the LDAPire GitHub repository .

Establishing Naming context with NMAP:

<SNIP>
PORT    STATE SERVICE    VERSION
389/tcp open  ldap       Microsoft Windows Active Directory LDAP (Domain: MEGABANK.LOCAL, Site: Default-First-Site-Name)
| ldap-rootdse:
| LDAP Results
|   <ROOT>
|       domainFunctionality: 7
|       forestFunctionality: 7
|       domainControllerFunctionality: 7
|       rootDomainNamingContext: DC=MEGABANK,DC=LOCAL
|       ldapServiceName: MEGABANK.LOCAL:[email protected]
<SNIP>

Enumerating LDAP using ldapsearch:

Enumerate LDAP naming context server name and domain name:

ldapsearch -H ldap://[IP] -x -s base namingcontexts
ldapsearch -H ldap://10.129.228.111 -x -s base namingcontexts

Check for anonymous bind using ldapsearch:

ldapsearch -H ldap://[IP] -x -b "dc=[DOMAIN],dc=[DOMAIN]"
ldapsearch -H ldap://10.129.1.207 -x -b "dc=sugarape,dc=local"

Enumerate entire domain with ldapsearch:


ldapsearch -H ldap://[LDAPName] -x -b "DC=[DCName],DC=[DCNAME]"  >> ldapDump.txt
ldapsearch -H ldap://monteverde.MEGABANK.LOCAL -x -b "DC=MEGABANK,DC=LOCAL"  >> ldapDump.txt

Enumerating LDAP using windapsearch:

Check for anonymous bind using windapsearch:

python3 windapsearch.py --dc-ip [IP] -u "" --functionality
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" --functionality

Enumerate all users using windapsearch:

python3 windapsearch.py --dc-ip [DC-IP] -u "" -U
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" -U

Enumerate all computers using windapsearch:

python3 windapsearch.py --dc-ip [DC-IP] -u "" -C
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" -C

Enumerate all groups using windapsearch:

python3 windapsearch.py --dc-ip [DC-IP] -u "" -G
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" -G

Scripts for Querying LDAP:

From within a python console:

from ldap3 import *

s = Server('[IP]',get_info = ALL)
c =  Connection(s, '', '')
c.bind()
## If it returns true we can run the next command it will return all LDAP information
s.info

Simple Python Script:

from ldap3 import *

srver = input("Enter IP of DC ")

s = Server(srver,get_info = ALL)
c =  Connection(s, '', '')

checkserver = c.bind()

if checkserver == True:
   print(s.info)
else:
    "Server does not allow LDAP Anonymous bind"

More advanced python script that allows passing username and passwords, also implements SSL:

from ldap3 import *
import re
import argparse
import logging
import getpass

def is_valid_ip(ip):
    pattern = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
    return pattern.match(ip) is not None

# Setup logging
logging.basicConfig(filename='ldap_test.log', level=logging.INFO)

# Command-line argument parsing
parser = argparse.ArgumentParser(description="LDAP Anonymous Bind Test")
parser.add_argument('dc_ip', help="IP address of the Domain Controller")
parser.add_argument('-u', '--user', help="Username for authentication", default='')
parser.add_argument('-p', '--password', help="Password for authentication", default='')

args = parser.parse_args()

# Validate IP address
srver = args.dc_ip
if not is_valid_ip(srver):
    print("Invalid IP address format.")
    logging.error(f"Invalid IP address format: {srver}")
    exit(1)

# Handle secure password input
user = args.user
password = args.password
if not password and user:
    password = getpass.getpass("Enter password: ")

# Function to attempt LDAP connection
def attempt_connection(use_ssl):
    try:
        s = Server(srver, use_ssl=use_ssl, get_info=ALL)
        c = Connection(s, user, password)
        checkserver = c.bind()
        return s, c, checkserver
    except Exception as e:
        logging.error(f"Error connecting to the server with SSL={use_ssl}: {e}")
        return None, None, False

# Attempt to connect with SSL first
print(f"Attempting to connect to {srver} with SSL...")
logging.info(f"Attempting to connect to {srver} with SSL")
s, c, checkserver = attempt_connection(use_ssl=True)

# If SSL connection fails, retry without SSL
if not checkserver:
    print("Failed to connect with SSL. Retrying without SSL...")
    logging.warning("Failed to connect with SSL. Retrying without SSL...")
    s, c, checkserver = attempt_connection(use_ssl=False)

# Final status check
if checkserver:
    print("Connected successfully. Retrieving server information...")
    logging.info("Connected successfully")
    print(s.info)
else:
    print("Failed to connect: Server does not allow LDAP Anonymous bind or invalid credentials.")
    logging.error("Failed to connect: Server does not allow LDAP Anonymous bind or invalid credentials.")

Powershell Script to query LDAP:

# Create a DirectoryEntry object for the LDAP path
$ldapPath = "LDAP://dc=sugarape,dc=local"
$directoryEntry = New-Object System.DirectoryServices.DirectoryEntry($ldapPath)

# Create a DirectorySearcher object
$searcher = New-Object System.DirectoryServices.DirectorySearcher($directoryEntry)

# Define the LDAP filter
$searcher.Filter = "(&(objectclass=pkicertificatetemplate)(!(mspki-enrollmentflag:1.2.840.113556.1.4.804:=2))(|(mspki-ra-signature=0)(!(mspki-rasignature=*)))(|(pkiextendedkeyusage=2.5.29.37.0)(!(pkiextendedkeyusage=*))))"

# Search in the entire subtree
$searcher.SearchScope = "Subtree"

# Find all matching entries
$results = $searcher.FindAll()

# Iterate through the results and display the properties
foreach ($result in $results) {
    $entry = $result.GetDirectoryEntry()
    $entry.Properties | foreach {
        Write-Output "$($_.PropertyName) = $($_.Value)"
    }
}

+Attacking LDAP+

LDAP Credential Stealing:

Password Spraying & Bruteforcing LDAP:

LDAP bruteforcing with hydra:

LDAP Injection:

Attack Explained.

Example of Vulnerable Code


$username = $_POST['username'];
$password = $_POST['password'];
$filter = "(&(uid=$username)(userPassword=$password))";
$result = ldap_search($ldapconn, $basedn, $filter);

Real-world Example

Prevention Techniques

  1. Input Validation: Sanitize and validate all user inputs before using them in LDAP queries.
  2. Use Bind Operations: Instead of constructing search filters with user input, use LDAP bind operations for authentication.
  3. Escape Special Characters: Use LDAP-specific escaping functions to neutralize special characters in user input.
  4. Implement Least Privilege: Ensure LDAP accounts have minimal necessary permissions.
  5. Use Prepared Statements: If available in your programming language, use LDAP prepared statements to separate queries from data.

Fix

Additional Resources

Troubleshooting LDAP

Common LDAP issues and their solutions:

  1. Connection Failures

    • Check network connectivity
    • Verify LDAP server is running
    • Ensure correct LDAP URL (ldap:// or ldaps://)
    • Check firewall settings
  2. Authentication Issues

    • Verify correct bind DN and password
    • Check user account status (not locked or disabled)
    • Ensure user has necessary permissions
  3. Search Problems

    • Verify correct base DN
    • Check search filter syntax
    • Ensure attributes being searched exist in schema
  4. SSL/TLS Issues

    • Verify SSL certificates are valid and trusted
    • Check SSL/TLS configuration on both client and server
  5. Performance Problems

    • Optimize search filters
    • Use indexing on frequently searched attributes
    • Consider implementing connection pooling
  6. Schema Violations

    • Ensure all required attributes are provided
    • Check attribute syntax and value constraints
  7. Replication Issues

    • Check network connectivity between replicas
    • Verify replication agreements are correctly configured
    • Check for conflicting updates

Tools for LDAP Troubleshooting:

LDAP Security Best Practices

  1. Use LDAPS (LDAP over SSL/TLS) to encrypt communications

  2. Implement strong authentication methods (e.g., SASL)

  3. Apply the principle of least privilege for LDAP accounts

  4. Regularly audit and update LDAP configurations

  5. Use input validation and parameterized queries to prevent LDAP injection

  6. Implement proper password policies

  7. Monitor LDAP logs for suspicious activities

  8. Keep LDAP software and related components up to date

  9. Use firewalls to restrict LDAP access to authorized hosts only

  10. Implement account lockout policies to prevent brute-force attacks

  11. Fix:

    • To mitigate the risks associated with LDAP injection attacks, it is crucial to thoroughly validate and sanitize user input before incorporating it into LDAP queries. This process should involve removing LDAP-specific special characters like * and employing parameterised queries to ensure user input is treated solely as data, not executable code.

LDAP Boxes on HTB:



Next: Attacking RPC: Deep Dive & Cheat Sheet