Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDAP search strings not escaped correctly #237

Closed
m-erhardt opened this issue Jul 9, 2024 · 0 comments · Fixed by #238
Closed

LDAP search strings not escaped correctly #237

m-erhardt opened this issue Jul 9, 2024 · 0 comments · Fixed by #238
Labels

Comments

@m-erhardt
Copy link
Contributor

Bug description

I use ldapauthenticator==1.3.2 to authenticate against our corporate Active Directory via the following config:

c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'

c.LDAPAuthenticator.server_address = 'our.companys.ldap.server'
c.LDAPAuthenticator.server_port = 636
c.LDAPAuthenticator.use_ssl = True

c.LDAPAuthenticator.lookup_dn = True
c.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'
c.LDAPAuthenticator.lookup_dn_search_user = 'CN=jupyter.bind,OU=Users,DC=our,DC=company'
c.LDAPAuthenticator.lookup_dn_search_password = '***'
c.LDAPAuthenticator.user_search_base = 'DC=our,DC=company'
c.LDAPAuthenticator.user_attribute = 'sAMAccountName'
c.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'cn'
c.LDAPAuthenticator.escape_userdn = False
c.LDAPAuthenticator.use_lookup_dn_username = False

c.LDAPAuthenticator.allowed_groups = [
    "CN=JupyterHubGroup,OU=Groups,DC=our,DC=company",
]

Within our Active Directory our CN usually is formated as Lastname\, Firstname but due to circumstances unknown to me some CN's are formatted as Lastname\, Firstname (Location)

As ldapauthenticator does not escape ldap search queries correctly (RFC4515) this leads to the following exception when authenticating with a User that has a non-escaped character in its CN/DN:

[E 2024-07-02 15:18:05.069 JupyterHub web:1875] Uncaught exception POST /hub/login?next=%2Fhub%2F (123.123.123.123)
    HTTPServerRequest(protocol='https', host='our.jupyterhub.fqdn:443', method='POST', uri='/hub/login?next=%2Fhub%2F', version='HTTP/1.1', remote_ip='123.123.123.123')
    Traceback (most recent call last):
      File "/home/hub/venv/lib/python3.12/site-packages/tornado/web.py", line 1790, in _execute
        result = await result
                 ^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/jupyterhub/handlers/login.py", line 164, in post
        user = await self.login_user(data)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/jupyterhub/handlers/base.py", line 964, in login_user
        authenticated = await self.authenticate(data)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/jupyterhub/auth.py", line 661, in get_authenticated_user
        authenticated = await maybe_future(self.authenticate(handler, data))
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/ldapauthenticator/ldapauthenticator.py", line 443, in authenticate
        found = conn.search(
                ^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/ldap3/core/connection.py", line 838, in search
        request = search_operation(search_base,
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/ldap3/operation/search.py", line 371, in search_operation
        request['filter'] = compile_filter(parse_filter(search_filter, schema, auto_escape, auto_encode, validator, check_names).elements[0])  # parse the searchFilter string and compile it starting from the root node
                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/hub/venv/lib/python3.12/site-packages/ldap3/operation/search.py", line 214, in parse_filter
        raise LDAPInvalidFilterError('malformed filter')
    ldap3.core.exceptions.LDAPInvalidFilterError: malformed filter

Setting c.LDAPAuthenticator.escape_userdn = True breaks the whole authentification as it results in the Bind DN beeing escaped as well but fails to escape the DN in the allowed_groups-lookup.
The debug log shows the following:

[D 2024-07-04 09:19:06.507 JupyterHub ldapauthenticator:384] Attempting to bind CN=Doe\, Jane,OU=Users,DC=our,DC=company with CN=Doe\5c, Jane,OU=Users,DC=our,DC=company
[D 2024-07-04 09:19:06.540 JupyterHub ldapauthenticator:397] Status of user bind CN=Doe\, Jane,OU=Users,DC=our,DC=company with CN=Doe\5c, Jane,OU=Users,DC=our,DC=company : False
    LDAPBindError: automatic bind not successful - invalidCredentials

Initially I thought this boils down to a difference between ActiveDirectory and OpenLDAP and spun up an OpenLDAP instance within a Docker Container.
Turns out: OpenLDAP (OpenLDAP: slapd 2.6.8 (May 22 2024 06:40:44)) behaves the same.

It expects special characters to be unescaped when used within the Bind DN but wants them escaped when beeing used within an LDAP filter.

How to reproduce

See above

Expected behaviour

Escape all user- or ldap-providedes fields in accordance with RFC4515 when constructing ldap search filters.

Actual behaviour

See above

Your personal set up

See above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant