Coordinated Disclosure Timeline

Summary

Redash is vulnerable to LDAP injection which may allow password spraying.

Project

Redash

Tested Version

v10.1.0

Details

LDAP injection in auth_ldap_user (GHSL-2024-009)

Starting from v3, Redash supports LDAP/AD authentication. The email from the user input is used to format a LDAP filter query string [1] that allows unauthenticated attacker to alter the filter query.

def auth_ldap_user(username, password):
    server = Server(settings.LDAP_HOST_URL, use_ssl=settings.LDAP_SSL)
    if settings.LDAP_BIND_DN is not None:
        conn = Connection(
            server,
            settings.LDAP_BIND_DN,
            password=settings.LDAP_BIND_DN_PASSWORD,
            authentication=settings.LDAP_AUTH_METHOD,
            auto_bind=True,
        )
    else:
        conn = Connection(server, auto_bind=True)

    conn.search(
        settings.LDAP_SEARCH_DN,
        settings.LDAP_SEARCH_TEMPLATE % {"username": username}, # [1]
        attributes=[settings.LDAP_DISPLAY_NAME_KEY, settings.LDAP_EMAIL_KEY],
    )

    if len(conn.entries) == 0:
        return None

    user = conn.entries[0] # [2]

    if not conn.rebind(user=user.entry_dn, password=password):
        return None

    return user

Impact

The LDAP authentication is disabled by default and has to be enabled by Redash administrator.

Another limiting factor is that the default value of settings.LDAP_SEARCH_TEMPLATE is (cn=%(username)s) and the ldap3 library, currently used by Redash, is quite strict about the number of parenthesis and also allows only one root query object. I.e. usual LDAP injection payloads that result in a query filter with nonmatching parenthesis or multiple root query objects won’t be accepted.

Nevertheless the settings.LDAP_SEARCH_TEMPLATE is configurable by Redash administrators and Redash provides instructions on how to customize it for integration with a specific LDAP provider. In case the setting is set to a LDAP filter that starts with logical AND & or logical OR | operator from the list of popular LDAP filters, for example (&(objectClass=inetOrgPerson)(userPassword=*)(cn=%(username)s)) to narrow only to users with passwords, the injection allows to create a valid filter query that will pass ldap3 checks.

As a result it is possible to use it for a variation of password spray attack - brute-force all users’ passwords with a single request. For the proof of concept:

    settings.LDAP_HOST_URL = "ldap.forumsys.com:389"
    settings.LDAP_BIND_DN = "cn=read-only-admin,dc=example,dc=com"
    settings.LDAP_BIND_DN_PASSWORD = "password"
    settings.LDAP_SEARCH_DN = "dc=example,dc=com"
    settings.LDAP_SEARCH_TEMPLATE = "(&(objectClass=inetOrgPerson)(userPassword=*)(cn=%(username)s))"
    auth_ldap_user("*)(userPassword={SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=", "password")

Since at [2] Redash always picks up the first found user, a second request may inject logical negation ! with the found user name to find all other users with the same password.

CVE

Credit

This issue was discovered and reported by GHSL team members @jorgectf (Jorge Rosillo) and @JarLob (Jaroslav Lobačevski).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2024-009 in any communication regarding this issue.