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
# 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
python3
from ldap3 import *
s = Server('10.129.1.207', get_info=ALL)
c = Connection(s, '', '')
c.bind()
s.infoSi 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
ldapwhoami -x -H ldap://<target>ldapsearch — sin credenciales
# 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=*)" commentEl flag -x indica simple authentication (anonymous). Sin credenciales no especificas -D ni -w.
windapsearch — sin credenciales
# 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
# Info del servidor y confirmar null bind
python3 ldapsearch-ad.py -l 10.129.1.207 -t infoPara enumeración adicional requiere credenciales válidas.
Null Bind — credenciales vacías explícitas
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.
# Listener simple para capturar auth LDAP
sudo nc -lvnp 389
# O con Responder para capturar y mostrar en formato legible
sudo responder -I eth0LDAP Injection (en aplicaciones web)
Si una aplicación usa LDAP para autenticación y no sanitiza la entrada:
# 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_cosaLa 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
# 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*)" sAMAccountNameBuscar usuario que cumpla dos condiciones a la vez (cruce)
# 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 descriptionBuscar la OU de un usuario específico
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" distinguishedNameEl 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
# 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)" sAMAccountNameEl flag -s onelevel equivale a SearchScope OneLevel y -s sub equivale a SubTree.
Buscar todos los grupos a los que pertenece un usuario
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" memberOfDevuelve 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)
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(cn=Help Desk)" memberDevuelve el campo member del grupo con los DNs de todos sus miembros.
Buscar computadoras por sistema operativo
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=computer)" operatingSystem operatingSystemVersion dNSHostNameBuscar OUs disponibles en el dominio
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=organizationalUnit)" ou distinguishedNameBuscar un objeto por parte de su nombre (wildcard)
# 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 dNSHostNameContar resultados sin mostrarlos
ldapsearch -x -H ldap://10.129.1.207 -b "dc=inlanefreight,dc=local" "(objectClass=user)" sAMAccountName | grep "sAMAccountName:" | wc -lSintaxis de ldapsearch — referencia rápida
# 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
# 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"# 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)" sAMAccountNamewindapsearch — con credenciales
# 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 -hTe pedirá la contraseña interactivamente. Puedes pasarla directamente con -p password aunque es menos seguro.
ldapsearch-ad.py — con credenciales
# 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 -hPython con ldap3 — búsqueda autenticada
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_EXPIREDLos 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.
❯ 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>)" sAMAccountNameBúsquedas cruzadas y misceláneas
Buscar usuarios miembros de un grupo específico
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)" sAMAccountNameCruce de dos condiciones — usuario que cumpla A y B simultáneamente
# 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))" sAMAccountNameBuscar la OU de un usuario específico
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" distinguishedNameEl 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
# 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)" sAMAccountNameBuscar todos los grupos a los que pertenece un usuario
ldapsearch -x -H ldap://10.129.1.207 -D "cn=admin,dc=inlanefreight,dc=local" -w password -b "dc=inlanefreight,dc=local" "(sAMAccountName=forend)" memberOfBuscar miembros de un grupo
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)" memberBuscar OUs disponibles en el dominio
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 distinguishedNameBuscar con wildcard
# 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 dNSHostNameContar resultados
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 -lConsideraciones 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:
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)" distinguishedNameEl 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
sudo apt install krb5-user libsasl2-modules-gssapi-mit -yConfigurar /etc/krb5.conf
[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.LOCALEl default_realm debe ir en MAYÚSCULAS. Si el hostname no resuelve, agregar al /etc/hosts:
echo "192.168.1.10 dc01.corp.local corp.local" | sudo tee -a /etc/hostsSincronizar tiempo con el DC (obligatorio)
sudo ntpdate dc01.corp.localKerberos rechaza tickets si la diferencia de tiempo entre cliente y KDC supera los 5 minutos.
Obtener TGT
# Con contraseña
kinit john.smith@CORP.LOCAL
# Verificar que el ticket está en el ccache
klistOutput 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.LOCALUna 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
# 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 ccacheConfirmación rápida — intentar con credenciales simples
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, v2580Ese error es la señal definitiva de que necesitas GSSAPI.
Estructura base del comando
ldapsearch [auth] -H ldap://<DC> -b "<base_dn>" "<filtro>" [atributos]# 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
# 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)) NOTOID 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
# 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)" distinguishedNameGrupos
# 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)" distinguishedNameComputadoras
# 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 distinguishedNameDominio y política de contraseñas
# 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 trustAttributesOUs y contenedores
# 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))" distinguishedNameObjetos eliminados
# 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 whenChangedConsultas cruzadas
# 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)" sAMAccountNameFiltrar y procesar output
# 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.ldifValores 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