CVE-2025-61911
python-ldap: LDAP injection in escape_filter_chars via crafted list/dict.
- CVSS 5.5
- CWE-75
- Input Validation and Sanitization
- Remote
python-ldap is a lightweight directory access protocol (LDAP) client API for Python. In versions prior to 3.4.5, the sanitization method `ldap.filter.escape_filter_chars` can be tricked to skip escaping of special characters when a crafted `list` or `dict` is supplied as the `assertion_value` parameter, and the non-default `escape_mode=1` is configured. The method `ldap.filter.escape_filter_chars` supports 3 different escaping modes. `escape_mode=0` (default) and `escape_mode=2` happen to raise exceptions when a `list` or `dict` object is supplied as the `assertion_value` parameter. However, `escape_mode=1` computes without performing adequate logic to ensure a fully escaped return value. If an application relies on the vulnerable method in the `python-ldap` library to escape untrusted user input, an attacker might be able to abuse the vulnerability to launch ldap injection attacks which could potentially disclose or manipulate ldap data meant to be inaccessible to them. Version 3.4.5 fixes the issue by adding a type check at the start of the `ldap.filter.escape_filter_chars` method to raise an exception when the supplied `assertion_value` parameter is not of type `str`.
- CWE
- CWE-75
- CVSS base score
- 5.5
- Published
- 2025-10-10
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- python-ldap
- Fixed by upgrading
- Yes
Solution
Upgrade `python-ldap` to version 3.4.5 or later.
Vulnerable code sample
import ldap.filter
# This code demonstrates the vulnerability in python-ldap versions prior to 3.4.5.
# A vulnerable version (e.g., 3.4.4) is required to run this code successfully.
# pip install python-ldap==3.4.4
# 1. Attacker-controlled input is provided as a list, which the vulnerable
# function does not handle correctly. The payload is designed to inject
# a wildcard search for any user.
malicious_payload = ["user1)(uid=*", ")"]
# 2. The application uses ldap.filter.escape_filter_chars with the non-default
# `escape_mode=1`. In vulnerable versions, this mode processes a list
# by simply concatenating its elements without escaping special characters.
unescaped_value = ldap.filter.escape_filter_chars(
assertion_value=malicious_payload,
escape_mode=1
)
# 3. The application constructs the final LDAP filter, assuming the user input
# has been properly sanitized.
ldap_search_filter = f"(cn={unescaped_value})"
# 4. The resulting filter is malformed due to the lack of escaping.
# Instead of searching for a user whose 'cn' is literally "user1)(uid=*)",
# the injected filter becomes "(cn=user1)(uid=*)", which searches for a
# user with 'cn' of 'user1' AND any user with a 'uid' attribute.
print(f"Intended Filter: (cn=user1\\29\\28uid\\3d*\\29)")
print(f"Injected Filter: {ldap_search_filter}")Patched code sample
def escape_filter_chars(assertion_value, escape_mode=0):
"""
A conceptual representation of the fixed ldap.filter.escape_filter_chars
method as of version 3.4.5.
"""
# FIX: A type check is added at the beginning of the method.
# This check ensures that the function raises a TypeError if the input
# is not a string, regardless of the escape_mode. This prevents crafted
# list or dict objects from reaching the flawed logic path that previously
# existed for escape_mode=1.
if not isinstance(assertion_value, str):
raise TypeError(
"assertion_value must be a string, not %s" % type(assertion_value).__name__
)
# The original escaping logic follows. This part of the function is
# now protected by the type check above. This is a simplified
# representation for demonstration purposes.
if escape_mode == 1:
# Simplified representation of escaping logic for mode 1
escaped_value = assertion_value.replace('\\', '\\5c')
escaped_value = escaped_value.replace('*', '\\2a')
escaped_value = escaped_value.replace('(', '\\28')
escaped_value = escaped_value.replace(')', '\\29')
escaped_value = escaped_value.replace('\x00', '\\00')
return escaped_value
else: # Default mode (0) or mode 2
# Simplified representation of the default escaping logic
escaped_value = assertion_value.replace('\\', '\\5c')
escaped_value = escaped_value.replace(',', '\\2c')
escaped_value = escaped_value.replace('*', '\\2a')
escaped_value = escaped_value.replace('(', '\\28')
escaped_value = escaped_value.replace(')', '\\29')
escaped_value = escaped_value.replace('\x00', '\\00')
return escaped_valuePayload
['*))(|(uid=*)']
Cite this entry
@misc{vaitp:cve202561911,
title = {{python-ldap: LDAP injection in escape_filter_chars via crafted list/dict.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-61911},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-61911/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
