VAITP Dataset

← Back to the dataset

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.

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_model

Patched 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_enabled

Cite 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 ::