Skip to content

LDAP Anonymous Bind

¿Qué es?

Un LDAP anonymous bind permite a un atacante no autenticado consultar información del dominio sin necesitar credenciales. Hosts Linux con OpenLDAP y appliances como vCenter suelen estar configurados así por defecto.

Lo que puedes obtener sin credenciales: listado completo de usuarios, grupos, computadoras, atributos de cuentas, política de contraseñas, y a veces contraseñas en campos de descripción. Con esa info puedes montar password spraying o ASREPRoasting sin tener ninguna credencial inicial.

Puertos por defecto: 389 (LDAP), 636 (LDAPS), 3268 (Global Catalog).


Herramientas


Reconocimiento previo — detección del servicio

c
# Detectar LDAP con nmap
nmap -p 389,636,3268 <target>

# Banner grabbing con nmap
nmap -p 389 -sV --script ldap-rootdse <target>

# Root DSE — info básica del servidor sin autenticación
ldapsearch -x -H ldap://<target> -b "" -s base "(objectclass=*)"

# Solo naming contexts
ldapsearch -x -H ldap://<target> -b "" -s base "(objectclass=*)" namingContexts

# Todos los atributos del Root DSE
ldapsearch -x -H ldap://<target> -b "" -s base "(objectclass=*)" "*" "+"

El Root DSE es un objeto especial que todos los servidores LDAP exponen sin autenticación. Contiene namingContexts (base DNs del dominio), supportedLDAPVersion, dnsHostName del DC, y niveles funcionales del dominio.


Verificar si permite anonymous bind

Python con ldap3

c
python3
from ldap3 import *
s = Server('10.129.1.207', get_info=ALL)
c = Connection(s, '', '')
c.bind()
s.info

Si c.bind() devuelve True el servidor permite anonymous bind. s.info muestra naming contexts, nombre del DC y niveles funcionales.

ldapwhoami — probar bind anónimo

c
ldapwhoami -x -H ldap://<target>

ldapsearch — sin credenciales

c
# Dump completo de todos los objetos del dominio
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local"

# Filtrar solo usuarios
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=user)" sAMAccountName

# Filtrar solo grupos
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=group)" cn

# Filtrar solo computadoras
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=computer)" cn

# Domain Controllers
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=8192)" dNSHostName

# Domain Admins
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(cn=Domain Admins)" member

# Cuentas deshabilitadas
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=2)"

# Contraseñas que no expiran
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=65536)"

# Cuentas con SPN (Kerberoasteables)
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(&(objectClass=user)(servicePrincipalName=*))" sAMAccountName servicePrincipalName

# Cuentas ASREPRoasteables
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" sAMAccountName

# Cuentas con Unconstrained Delegation
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=524288)" sAMAccountName memberOf

# Contraseñas en campo description
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(description=*)" description

# Política de contraseñas
ldapsearch -H ldap://10.129.1.207 -x -b "dc=inlanefreight,dc=local" -s sub "*" | grep -m 1 -B 10 pwdHistoryLength

# Buscar OU de un usuario específico
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(sAMAccountName=kevin.gregory)" distinguishedName

# Info adicional — campo info y comment
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(info=*)" info
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(comment=*)" comment

El flag -x indica simple authentication (anonymous). Sin credenciales no especificas -D ni -w.


windapsearch — sin credenciales

c
# Verificar null session y nivel funcional del dominio
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" --functionality

# Listar todos los usuarios
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" -U

# Listar todas las computadoras
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" -C

# Domain Admins
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" --da

# Usuarios con Unconstrained Delegation
python3 windapsearch.py --dc-ip 10.129.1.207 -u "" -PU

-u "" indica anonymous bind. --functionality muestra el nivel funcional del dominio que revela la versión de Windows Server del DC: 7 = 2016, 6 = 2012 R2, 5 = 2008 R2.


ldapsearch-ad.py — info básica sin credenciales

c
# Info del servidor y confirmar null bind
python3 ldapsearch-ad.py -l 10.129.1.207 -t info

Para enumeración adicional requiere credenciales válidas.


Null Bind — credenciales vacías explícitas

c
ldapsearch -x -H ldap://<target> -D "" -w "" -b "dc=inlanefreight,dc=local"

Diferente del anonymous bind porque especifica explícitamente DN y password vacíos. Algunos servidores permiten uno pero no el otro.


Pass-Back Attack

Si puedes modificar la configuración de LDAP en un dispositivo (impresora, appliance), cambias el servidor LDAP a tu IP y capturas las credenciales que el dispositivo envía al intentar autenticarse.

c
# Listener simple para capturar auth LDAP
sudo nc -lvnp 389

# O con Responder para capturar y mostrar en formato legible
sudo responder -I eth0

LDAP Injection (en aplicaciones web)

Si una aplicación usa LDAP para autenticación y no sanitiza la entrada:

c
# Payload básico — bypass de autenticación
username: admin)(&(|
password: cualquier_cosa

# Wildcard para listar todos
username: *
password: *

# OR injection
username: *)(uid=*))(|(uid=*
password: cualquier_cosa

La query normal (&(uid=user)(password=pass)) se convierte en (&(uid=admin)(&(|)(password=cualquier_cosa)) que es siempre verdadera.


Búsquedas cruzadas y misceláneas

Buscar usuarios miembros de un grupo específico

c
# Usuarios en el grupo Protected Users
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(memberOf=CN=Protected Users,CN=Users,DC=inlanefreight,DC=local)" sAMAccountName

# Usuarios en Domain Admins
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(memberOf=CN=Domain Admins,CN=Users,DC=inlanefreight,DC=local)" sAMAccountName

# Usuarios en cualquier grupo que contenga "admin" en el nombre
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(memberOf=*admin*)" sAMAccountName

Buscar usuario que cumpla dos condiciones a la vez (cruce)

c
# Usuarios con Unconstrained Delegation que también estén en Protected Users
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(&(userAccountControl:1.2.840.113556.1.4.803:=524288)(memberOf=CN=Protected Users,CN=Users,DC=inlanefreight,DC=local))" sAMAccountName

# Usuarios con SPN que sean Domain Admins
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(&(servicePrincipalName=*)(memberOf=CN=Domain Admins,CN=Users,DC=inlanefreight,DC=local))" sAMAccountName servicePrincipalName

# Usuarios ASREPRoasteables que tengan description
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(&(userAccountControl:1.2.840.113556.1.4.803:=4194304)(description=*))" sAMAccountName description

Buscar la OU de un usuario específico

c
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" distinguishedName

El DistinguishedName en la respuesta se lee de izquierda a derecha. Por ejemplo CN=forend,OU=IT Admins,OU=IT,DC=INLANEFREIGHT,DC=LOCAL significa que forend está en la OU IT Admins que a su vez está dentro de la OU IT.

Buscar todos los usuarios dentro de una OU específica

c
# Usuarios directamente en una OU (sin bajar a sub-OUs)
ldapsearch -x -H ldap://10.129.1.207 -b "OU=IT,DC=inlanefreight,DC=local" -s onelevel "(objectClass=user)" sAMAccountName

# Usuarios en una OU y todas sus sub-OUs
ldapsearch -x -H ldap://10.129.1.207 -b "OU=IT,DC=inlanefreight,DC=local" -s sub "(objectClass=user)" sAMAccountName

El flag -s onelevel equivale a SearchScope OneLevel y -s sub equivale a SubTree.

Buscar todos los grupos a los que pertenece un usuario

c
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" memberOf

Devuelve el campo memberOf del usuario con todos sus grupos. No incluye membresía anidada, solo grupos directos.

Buscar miembros de un grupo (listar quién está dentro)

c
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(cn=Help Desk)" member

Devuelve el campo member del grupo con los DNs de todos sus miembros.

Buscar computadoras por sistema operativo

c
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=computer)" operatingSystem operatingSystemVersion dNSHostName

Buscar OUs disponibles en el dominio

c
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=organizationalUnit)" ou distinguishedName

Buscar un objeto por parte de su nombre (wildcard)

c
# Usuarios cuyo nombre empiece con "john"
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(sAMAccountName=john*)" sAMAccountName

# Grupos que contengan "admin" en el nombre
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(cn=*admin*)" cn

# Computadoras que contengan "SQL" en el nombre
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(&(objectClass=computer)(cn=*SQL*))" cn dNSHostName

Contar resultados sin mostrarlos

c
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=user)" sAMAccountName | grep "sAMAccountName:" | wc -l

Sintaxis de ldapsearch — referencia rápida

c
# Estructura base
ldapsearch -x -H ldap://<target> -b "<base_dn>" "<filtro>" <atributos>

# AND: (&(condicion1)(condicion2))
ldapsearch -x -H ldap://<target> -b "dc=example,dc=com" "(&(objectClass=user)(cn=admin))"

# OR: (|(condicion1)(condicion2))
ldapsearch -x -H ldap://<target> -b "dc=example,dc=com" "(|(objectClass=user)(objectClass=group))"

# NOT: (!(condicion))
ldapsearch -x -H ldap://<target> -b "dc=example,dc=com" "(!(cn=guest))"

# Wildcard
ldapsearch -x -H ldap://<target> -b "dc=example,dc=com" "(cn=admin*)"

# Case sensitive (extensible match)
ldapsearch -x -H ldap://<target> -b "dc=example,dc=com" "cn:caseExactMatch:=john"

Credentialed LDAP Enumeration

¿Qué cambia con credenciales?

Con credenciales válidas de dominio puedes extraer mucha más información que con anonymous bind: membresía de grupos privilegiados, GPOs, trusts, política de contraseñas detallada, atributos completos de usuarios y computadoras, y ejecutar búsquedas que el servidor restringe a usuarios autenticados.


Herramientas


ldapsearch — con credenciales

c
# Opción 1 - Bind con usuario y contraseña
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local"

# Opcion 2 - Bind con usuario y contraseña
ldapsearch -x -H ldap://10.129.1.207 -D "admin@inlanefreight.local" -w password -b "dc=inlanefreight,dc=local"

# Opcion 3 - Bind con usuario y contraseña
ldapsearch -x -H ldap://10.129.1.207 -D "inlanefreight\admin" -w password -b "dc=inlanefreight,dc=local"

# Opcion 4 - Bind con usuario y contraseña
ldapsearch -x -H ldap://10.129.1.207 -D "CN=hugo Smith,CN=Users,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local"
c
# Pedir contraseña interactivamente (más seguro)
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -W -b "dc=inlanefreight,dc=local"

# LDAPS (SSL/TLS, puerto 636)
ldapsearch -x -H ldaps://10.129.1.207:636 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local"

# Dump completo de todos los usuarios con atributos específicos
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(objectClass=user)" sAMAccountName mail userAccountControl

# Guardar dump completo a archivo
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(objectClass=user)" "*" "+" > all_users.ldif

# Buscar contraseñas en el dump
grep -i "description:" all_users.ldif | grep -i "pass\|pwd\|secret"

# Kerberoasteables
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(objectClass=user)(servicePrincipalName=*))" sAMAccountName servicePrincipalName

# ASREPRoasteables
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))" sAMAccountName

# Cuentas con cifrado reversible (ENCRYPTED_TEXT_PWD_ALLOWED)
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=128)" sAMAccountName

# Smartcard required
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=262144)" sAMAccountName

# Cuentas con PASSWD_NOTREQD
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=32)" sAMAccountName

windapsearch — con credenciales

c
# Domain Admins
python3 windapsearch.py --dc-ip 10.129.1.207 -u inlanefreight\\james.cross --da

# Usuarios con Unconstrained Delegation
python3 windapsearch.py --dc-ip 10.129.1.207 -d inlanefreight.local -u inlanefreight\\james.cross --unconstrained-users

# Computadoras con Unconstrained Delegation
python3 windapsearch.py --dc-ip 10.129.1.207 -d inlanefreight.local -u inlanefreight\\james.cross --unconstrained-computers

# Usuarios privilegiados (todos los grupos admin, recursivo)
python3 windapsearch.py --dc-ip 10.129.1.207 -u inlanefreight\\james.cross -PU

# Todos los usuarios
python3 windapsearch.py --dc-ip 10.129.1.207 -u inlanefreight\\james.cross -U

# Todas las computadoras
python3 windapsearch.py --dc-ip 10.129.1.207 -u inlanefreight\\james.cross -C

# Ver todas las opciones
python3 windapsearch.py -h

Te pedirá la contraseña interactivamente. Puedes pasarla directamente con -p password aunque es menos seguro.


ldapsearch-ad.py — con credenciales

c
# Política de contraseñas
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t pass-pols

# Kerberoasteables
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t kerberoast | grep servicePrincipalName:

# ASREPRoasteables
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t asreproast

# Admins del dominio
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t show-admins

# Info de un usuario específico
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t show-user -s "(sAMAccountName=forend)"

# Trusts del dominio
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t trusts

# Ejecutar todo de una vez
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t all

# Con filtro LDAP personalizado
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t search -s "(userAccountControl:1.2.840.113556.1.4.803:=262144)"

# Guardar output a archivo
python3 ldapsearch-ad.py -l 10.129.1.207 -d inlanefreight -u james.cross -p Summer2020 -t all -o output.txt

# Ver todas las opciones
python3 ldapsearch-ad.py -h

Python con ldap3 — búsqueda autenticada

c
python3
from ldap3 import Server, Connection, ALL
server = Server('10.129.1.207', get_info=ALL)
conn = Connection(server, 'cn=admin,dc=inlanefreight,dc=local', 'password', auto_bind=True)
conn.search('dc=inlanefreight,dc=local', '(objectClass=user)')
for entry in conn.entries:
    print(entry)
conn.unbind()

Valores clave de userAccountControl — referencia

2        ACCOUNTDISABLE
32       PASSWD_NOTREQD
64       PASSWD_CANT_CHANGE
128      ENCRYPTED_TEXT_PWD_ALLOWED  (cifrado reversible)
512      NORMAL_ACCOUNT
65536    DONT_EXPIRE_PASSWORD
262144   SMARTCARD_REQUIRED
524288   TRUSTED_FOR_DELEGATION      (Unconstrained Delegation)
4194304  DONT_REQ_PREAUTH            (ASREPRoasteable)
8388608  PASSWORD_EXPIRED

Los valores son acumulativos. NORMAL_ACCOUNT + ENCRYPTED_TEXT_PWD_ALLOWED = 512 + 128 = 640.

Para verificar el bitmask de un valor combinado, suma los valores de los flags activos. Por ejemplo useraccountcontrol = 66048 significa 512 + 65536 = NORMAL_ACCOUNT + DONT_EXPIRE_PASSWORD.

c
❯ ldapsearch -x -H ldap://10.129.19.102 -b "dc=inlanefreight,dc=local" "(userAccountControl:1.2.840.113556.1.4.803:=<Cambiar valor de referncia>)" sAMAccountName

Búsquedas cruzadas y misceláneas

Buscar usuarios miembros de un grupo específico

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(memberOf=CN=Protected Users,CN=Users,DC=inlanefreight,DC=local)" sAMAccountName

ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(memberOf=CN=Domain Admins,CN=Users,DC=inlanefreight,DC=local)" sAMAccountName

Cruce de dos condiciones — usuario que cumpla A y B simultáneamente

c
# Unconstrained Delegation + miembro de Protected Users
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(userAccountControl:1.2.840.113556.1.4.803:=524288)(memberOf=CN=Protected Users,CN=Users,DC=inlanefreight,DC=local))" sAMAccountName memberOf

# Kerberoasteable + Domain Admin
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(servicePrincipalName=*)(memberOf=CN=Domain Admins,CN=Users,DC=inlanefreight,DC=local))" sAMAccountName servicePrincipalName

# ASREPRoasteable + con description (posible contraseña)
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(userAccountControl:1.2.840.113556.1.4.803:=4194304)(description=*))" sAMAccountName description

# Admin (adminCount=1) + PASSWD_NOTREQD
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(adminCount=1)(userAccountControl:1.2.840.113556.1.4.803:=32))" sAMAccountName

Buscar la OU de un usuario específico

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" distinguishedName

El DistinguishedName se lee de izquierda a derecha: primero el CN del usuario, luego las OUs de más específica a más general, al final el dominio. CN=forend,OU=IT Admins,OU=IT,DC=INLANEFREIGHT,DC=LOCAL = usuario en OU IT Admins dentro de la OU IT.

Buscar todos los usuarios dentro de una OU

c
# Solo en esa OU sin bajar a sub-OUs
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "OU=IT,DC=inlanefreight,DC=local" -s onelevel "(objectClass=user)" sAMAccountName

# En esa OU y todas sus sub-OUs
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "OU=IT,DC=inlanefreight,DC=local" -s sub "(objectClass=user)" sAMAccountName

Buscar todos los grupos a los que pertenece un usuario

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" memberOf

Buscar miembros de un grupo

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(cn=Help Desk)" member

Buscar OUs disponibles en el dominio

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(objectClass=organizationalUnit)" ou distinguishedName

Buscar con wildcard

c
# Usuarios cuyo nombre empiece con "john"
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(sAMAccountName=john*)" sAMAccountName

# Grupos que contengan "admin"
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(cn=*admin*)" cn

# Computadoras con SQL en el nombre
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(&(objectClass=computer)(cn=*SQL*))" cn dNSHostName

Contar resultados

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(objectClass=user)" sAMAccountName | grep "sAMAccountName:" | wc -l

Consideraciones importantes

Con ldapsearch-ad.py -t pass-pols obtienes la política de contraseñas sin necesitar privilegios elevados. Sin embargo la política de contraseñas granular (Fine Grained Password Policy) requiere permisos más altos para leerla.

Con windapsearch --unconstrained-users te devuelve el DistinguishedName completo de los usuarios, lo que muestra directamente en qué OU están y si son cuentas de servicio, lo cual es más útil que solo el nombre.

Para encontrar la OU de un usuario específico via LDAP autenticado:

c
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(sAMAccountName=kevin.gregory)" distinguishedName

El DistinguishedName en la respuesta se lee de izquierda a derecha: primero el CN del usuario, luego las OUs de más específica a más general, y al final el dominio.

LDAP Enumeration via ldapsearch — Kerberos & Credenciales

Requisitos previos

Instalar dependencias

c
sudo apt install krb5-user libsasl2-modules-gssapi-mit -y

Configurar /etc/krb5.conf

c
[libdefaults]
    default_realm = CORP.LOCAL
    dns_lookup_realm = false
    dns_lookup_kdc = false

[realms]
    CORP.LOCAL = {
        kdc = dc01.corp.local
        admin_server = dc01.corp.local
    }

[domain_realm]
    .corp.local = CORP.LOCAL
    corp.local = CORP.LOCAL

El default_realm debe ir en MAYÚSCULAS. Si el hostname no resuelve, agregar al /etc/hosts:

c
echo "192.168.1.10 dc01.corp.local corp.local" | sudo tee -a /etc/hosts

Sincronizar tiempo con el DC (obligatorio)

c
sudo ntpdate dc01.corp.local

Kerberos rechaza tickets si la diferencia de tiempo entre cliente y KDC supera los 5 minutos.

Obtener TGT

c
# Con contraseña
kinit john.smith@CORP.LOCAL

# Verificar que el ticket está en el ccache
klist

Output esperado de klist:

Credentials cache: FILE:/tmp/krb5cc_1000
        Principal: john.smith@CORP.LOCAL
  Issued              Expires             Principal
Jun 14 03:21:00  Jun 14 13:21:00  krbtgt/CORP.LOCAL@CORP.LOCAL

Una vez tienes el TGT todas las herramientas que soporten Kerberos lo usan automáticamente desde el ccache. No necesitas pasar credenciales en cada comando.


Cuándo usar GSSAPI vs credenciales simples

Detectar si el DC exige LDAP signing

c
# Con nmap
nmap -p 389,636 --script ldap-rootdse 192.168.1.10

# Con netexec — el campo clave es "signing"
netexec ldap 192.168.1.10 -u john.smith -p 'Password123!'

Output de netexec:

LDAP  192.168.1.10  389  DC01  [*] Windows Server 2019 (name:DC01) (domain:corp.local) (signing:True) (channel binding:False)
LDAP  192.168.1.10  389  DC01  [+] corp.local\john.smith:Password123!

El campo signing:True indica que el DC requiere firma en los paquetes LDAP.

Tabla de decisión

signing:False  →  puedes usar -x con usuario y contraseña
signing:True   →  debes usar -Y GSSAPI con TGT en el ccache

Confirmación rápida — intentar con credenciales simples

c
ldapsearch -x -D "john.smith@corp.local" -w 'Password123!' \
  -H ldap://192.168.1.10 -b "DC=corp,DC=local" "(objectClass=domain)"

Si el servidor tiene signing obligatorio verás:

ldap_bind: Strong(er) authentication required (8)
	additional info: 00002028: LdapErr: DSID-0C09044E, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v2580

Ese error es la señal definitiva de que necesitas GSSAPI.


Estructura base del comando

c
ldapsearch [auth] -H ldap://<DC> -b "<base_dn>" "<filtro>" [atributos]
c
# Con credenciales simples
ldapsearch -x -D "john.smith@corp.local" -w 'Password123!' -H ldap://192.168.1.10 -b "DC=corp,DC=local" "<filtro>"

# Con Kerberos (LDAP signing habilitado)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local -b "DC=corp,DC=local" "<filtro>"

# Anónimo
ldapsearch -x -H ldap://192.168.1.10 -b "DC=corp,DC=local" "<filtro>"

Flags útiles

-x          autenticación simple (usuario/contraseña)
-Y GSSAPI   autenticación Kerberos (requiere TGT en ccache)
-D          bind DN (usuario)
-w          contraseña en texto
-W          pedir contraseña interactivamente
-H          URI del servidor LDAP
-b          base DN desde donde buscar
-s          scope: base | onelevel | sub (default: sub)
-L          output en formato LDIF (una L = LDIF, dos LL = sin comentarios, tres LLL = sin version)
-o ldif-wrap=no    deshabilitar el wrap de líneas largas
-z <num>    límite de resultados (0 = sin límite)

Scopes de búsqueda

c
# Solo el objeto base
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local -b "OU=IT,DC=corp,DC=local" -s base "(objectClass=*)"

# Solo un nivel dentro del base
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local -b "OU=IT,DC=corp,DC=local" -s onelevel "(objectClass=user)"

# Todo el subárbol (por defecto)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local -b "OU=IT,DC=corp,DC=local" -s sub "(objectClass=user)"

Sintaxis de filtros

(atributo=valor)          igual
(atributo=*)              existe (tiene cualquier valor)
(!(atributo=*))           no existe
(atributo=*texto*)        contiene
(atributo=texto*)         empieza con
(atributo=*texto)         termina con
(&(filtro1)(filtro2))     AND
(|(filtro1)(filtro2))     OR
(!(filtro))               NOT

OID para bits de userAccountControl

:1.2.840.113556.1.4.803:=   AND a nivel de bits (el flag DEBE estar activo)
:1.2.840.113556.1.4.804:=   OR a nivel de bits (al menos uno activo)

Usuarios

c
# Todos los usuarios
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=user)" sAMAccountName

# Usuario específico
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(sAMAccountName=john.smith)"

# Todos los atributos de un usuario
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(sAMAccountName=john.smith)" "*"

# Buscar por email
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(mail=john.smith@corp.local)" sAMAccountName mail

# Usuarios con descripción no vacía (buscar contraseñas)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=user)(description=*))" sAMAccountName description

# Usuarios con contraseña que no expira
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=65536)" sAMAccountName

# Usuarios deshabilitados
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=2)" sAMAccountName

# Usuarios sin contraseña requerida
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=32)" sAMAccountName

# ASREPRoasteables (no requieren pre-autenticación)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" sAMAccountName

# Kerberoasteables (tienen SPN)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=user)(servicePrincipalName=*))" sAMAccountName servicePrincipalName

# Unconstrained Delegation
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=524288)" sAMAccountName

# Smartcard requerida
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=262144)" sAMAccountName

# Cifrado reversible habilitado
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=128)" sAMAccountName

# Administradores (adminCount=1)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=user)(adminCount=1))" sAMAccountName

# Buscar la OU de un usuario
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(sAMAccountName=john.smith)" distinguishedName

Grupos

c
# Todos los grupos
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=group)" cn

# Grupo específico
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(cn=Domain Admins)" member

# Miembros de Domain Admins
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(memberOf=CN=Domain Admins,CN=Users,DC=corp,DC=local)" sAMAccountName

# Grupos a los que pertenece un usuario
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(sAMAccountName=john.smith)" memberOf

# Grupos con adminCount=1
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=group)(adminCount=1))" cn

# Grupos con members de otro dominio
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=foreignSecurityPrincipal)" distinguishedName

Computadoras

c
# Todas las computadoras
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=computer)" cn dNSHostName operatingSystem

# Domain Controllers
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=8192)" cn dNSHostName

# Computadoras con Unconstrained Delegation
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn

# Computadoras con Constrained Delegation
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=computer)(msDS-AllowedToDelegateTo=*))" cn msDS-AllowedToDelegateTo

# Buscar computadora por nombre
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=computer)(cn=SRV01))" cn dNSHostName distinguishedName

Dominio y política de contraseñas

c
# Info del dominio
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=domain)" *

# Política de contraseñas
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=domain)" minPwdLength pwdHistoryLength lockoutThreshold maxPwdAge minPwdAge

# Fine-grained password policies
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "CN=Password Settings Container,CN=System,DC=corp,DC=local" \
  "(objectClass=msDS-PasswordSettings)" *

# Trusts del dominio
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "CN=System,DC=corp,DC=local" "(objectClass=trustedDomain)" cn trustDirection trustType trustAttributes

OUs y contenedores

c
# Todas las OUs
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=organizationalUnit)" ou distinguishedName

# Buscar OU por nombre
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(&(objectClass=organizationalUnit)(ou=IT))" distinguishedName

Objetos eliminados

c
# Requiere el control LDAP 1.2.840.113556.1.4.417
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "CN=Deleted Objects,DC=corp,DC=local" \
  -E '!1.2.840.113556.1.4.417' \
  "(isDeleted=TRUE)" sAMAccountName distinguishedName whenChanged

Consultas cruzadas

c
# Usuarios con SPN que sean admins (Kerberoast + alto impacto)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" \
  "(&(objectClass=user)(servicePrincipalName=*)(adminCount=1))" sAMAccountName servicePrincipalName

# Usuarios ASREPRoasteables que NO sean Protected Users
# Paso 1 - obtener ASREPRoasteables
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" \
  "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" sAMAccountName memberOf

# Usuarios con Unconstrained Delegation que estén en Protected Users
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" \
  "(&(userAccountControl:1.2.840.113556.1.4.803:=524288)(memberOf=CN=Protected Users,CN=Users,DC=corp,DC=local))" sAMAccountName

# Usuarios en una OU específica
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "OU=IT,DC=corp,DC=local" \
  "(objectClass=user)" sAMAccountName

Filtrar y procesar output

c
# Solo valores de un atributo (sin encabezados)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=user)" sAMAccountName | grep "sAMAccountName:" | awk '{print $2}'

# Contar resultados
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=user)" sAMAccountName | grep -c "sAMAccountName:"

# Sin wrap de líneas (para atributos largos como base64)
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" -o ldif-wrap=no "(objectClass=user)" nTSecurityDescriptor

# Guardar output a archivo
ldapsearch -Y GSSAPI -H ldap://dc01.corp.local \
  -b "DC=corp,DC=local" "(objectClass=user)" * > usuarios.ldif

Valores de userAccountControl — referencia

2        ACCOUNTDISABLE
32       PASSWD_NOTREQD
64       PASSWD_CANT_CHANGE
128      ENCRYPTED_TEXT_PWD_ALLOWED  (cifrado reversible)
512      NORMAL_ACCOUNT
2048     INTERDOMAIN_TRUST_ACCOUNT
4096     WORKSTATION_TRUST_ACCOUNT
8192     SERVER_TRUST_ACCOUNT        (Domain Controller)
65536    DONT_EXPIRE_PASSWORD
262144   SMARTCARD_REQUIRED
524288   TRUSTED_FOR_DELEGATION      (Unconstrained Delegation)
4194304  DONT_REQ_PREAUTH            (ASREPRoasteable)

Los valores son acumulativos. Un valor de 66048 = 512 + 65536 = NORMAL_ACCOUNT + DONT_EXPIRE_PASSWORD.


Notas personales — ldapsearch