CVE-2026-40683
Keystone's LDAP backend treats disabled users as enabled, allowing auth.
- CVSS 7.7
- CWE-843
- Authentication, Authorization, and Session Management
- Remote
In OpenStack Keystone before 28.0.1, the LDAP identity backend does not convert the user enabled attribute to a boolean when the user_enabled_invert configuration option is False (the default). The _ldap_res_to_model method in the UserApi class only performed string-to-boolean conversion when user_enabled_invert was True. When False, the raw string value from LDAP (e.g., "FALSE") was used directly. Since non-empty strings are truthy in Python, users marked as disabled in LDAP were treated as enabled by Keystone, allowing them to authenticate and perform actions. All deployments using the LDAP identity backend without user_enabled_invert=True or user_enabled_emulation are affected.
- CWE
- CWE-843
- CVSS base score
- 7.7
- Published
- 2026-04-14
- OWASP
- A07 Identification and Authentication Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Insecure Authentication Mechanisms
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- OpenStack Ke
- Fixed by upgrading
- Yes
Solution
Upgrade to OpenStack Keystone version 28.0.1 or later.
Vulnerable code sample
import distutils.util
class UserApi:
def _ldap_res_to_model(self, ldap_data, user_enabled_invert=False):
"""
Simplified representation of the vulnerable method in Keystone.
`ldap_data` simulates a dictionary of attributes from an LDAP user entry.
"""
user_model = {
'name': ldap_data.get('username'),
}
# This simulates getting the raw string value of the 'enabled' attribute
# from the LDAP directory, which could be "TRUE" or "FALSE".
enabled_attr_from_ldap = ldap_data.get('user_enabled_attribute', 'TRUE')
if user_enabled_invert:
# This logic path correctly performed a string-to-boolean conversion.
user_model['enabled'] = not bool(distutils.util.strtobool(enabled_attr_from_ldap))
else:
# THE VULNERABILITY:
# When user_enabled_invert is False (the default), the raw string
# value from LDAP (e.g., "FALSE") was assigned directly. In Python,
# any non-empty string is truthy, so a user with an LDAP attribute
# value of "FALSE" was incorrectly treated as being enabled.
user_model['enabled'] = enabled_attr_from_ldap
return user_modelPatched code sample
def bool_from_string(s):
"""Helper function to mimic OpenStack's string-to-boolean conversion."""
if isinstance(s, bool):
return s
if not isinstance(s, str):
s = str(s)
val = s.strip().lower()
if val in ('true', 't', 'yes', 'y', '1'):
return True
if val in ('false', 'f', 'no', 'n', '0'):
return False
# For this example, non-empty but unrecognized strings are truthy,
# mimicking Python's general behavior which caused the vulnerability.
# A real implementation might be stricter.
return len(val) > 0
def _get_user_enabled_status_vulnerable(ldap_user, user_enabled_invert=False):
"""Represents the vulnerable logic."""
enabled_val = ldap_user.get('enabled', 'TRUE')
if user_enabled_invert:
# This path correctly converts to bool before inverting
return not bool_from_string(enabled_val)
else:
# VULNERABILITY: Returns the raw string "FALSE", which is truthy
return enabled_val
def _get_user_enabled_status_fixed(ldap_user, user_enabled_invert=False):
"""Represents the fixed logic."""
enabled_val = ldap_user.get('enabled', 'TRUE')
# FIX: The string value from LDAP is *always* converted to a boolean first.
is_enabled = bool_from_string(enabled_val)
# The inversion logic is then applied to the boolean result.
if user_enabled_invert:
return not is_enabled
else:
return is_enabledCite this entry
@misc{vaitp:cve202640683,
title = {{Keystone's LDAP backend treats disabled users as enabled, allowing auth.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-40683},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-40683/}}
}
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 ::
