Coordinated Disclosure Timeline
- 2024-02-01: The report was sent to maintainers.
- 2024-05-18: Fixed in this commit.
- 2024-05-18: The advisory was published and the announcement was made. The fix is going to be deployed in the next version.
Summary
Redash is vulnerable to LDAP injection which may allow password spraying.
Project
Redash
Tested Version
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:
- Setup LDAP to the free LDAP server.
- Issue the login request with the user name
*)(userPassword={SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
and the passwordpassword
. (TheW6ph5Mm5Pz8GgiULbPgzG37mj9g=
is base64 encoded SHA valuepassword
the attacker tries to guess.)
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
- CVE-2020-36144
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.