[GH-ISSUE #1073] [BUG] LDAP search for attribute values will only match lowercased strings. Attribute values with any uppercase will never match. #386

Closed
opened 2026-02-27 08:17:00 +03:00 by kerem · 1 comment
Owner

Originally created by @broeng on GitHub (Jan 12, 2025).
Original GitHub issue: https://github.com/lldap/lldap/issues/1073

Describe the bug
When storing attribute values, the values are stored in the DB in the case they are given, serialized with serde. I think this is correct behaviour.

When an LDAP search for a specific attribute value is done, the value in the search filter will have been lowercased, before it is serialized with Serde, and used for lookup in the user_attributes table. This means the serialized value will never match a value that was not also stored in lowercase.

To Reproduce
Steps to reproduce the behavior:

  1. Create a new custom user attribute, a string value ('sambaSID' in this example).
  2. Assign a value with uppercase letters to this attribute for a user. ('S-1-5-21-2293260629-1257142175-1903762185-3202' in this example).
  3. Search for this attribute and value with ldapsearch, with same casing. ldapsearch ... '(&(objectclass=person)(sambaSID=S-1-5-21-2293260629-1257142175-1903762185-3202))'
  4. Observe no results returned.

Expected behavior
Expected to get user back, with the given attribute and value.

Logs
Query from LLDAP - observe how value is lowercased in filter expression:

INFO LDAP request [ 1.07ms | 5.46% / 100.00% ] session_id: 557bc751-8cee-46f8-ac2a-6db7b5921e4e
DEBUG ┝━ 🐛 [debug]: | msg: LdapMsg { msgid: 2, op: SearchRequest(LdapSearchRequest { base: "dc=example,dc=com", scope: Subtree, aliases: Never, sizelimit: 0, timelimit: 0, typesonly: false, filter: And([Equality("objectclass", "person"), Equality("sambasid", "S-1-5-21-2293260629-1257142175-1903762185-3202")]), attrs: [] }), ctrl: [] }
DEBUG ┝━ do_search [ 1.01ms | 33.85% / 94.54% ]
DEBUG │ ┝━ 🐛 [debug]: | request.base: "dc=apc,dc=dk" | scope: Global
DEBUG │ ┝━ get_user_list [ 336µs | 1.92% / 31.48% ]
DEBUG │ │ ┝━ 🐛 [debug]: | filters: And([And([]), AttributeEquality(AttributeName(CaseInsensitiveString("sambasid")), Serialized("s-1-5-21-2293260629-1257142175-1903762185-3202"))])
DEBUG │ │ ┕━ list_users [ 316µs | 29.55% ] filters: Some(And([And([]), AttributeEquality(AttributeName(CaseInsensitiveString("sambasid")), Serialized("s-1-5-21-2293260629-1257142175-1903762185-3202"))])) | _get_groups: false
DEBUG │ │ ┕━ 🐛 [debug]: | return: []

From postgres log:

LOG: execute sqlx_s_1: SELECT "users"."user_id" AS "A_user_id", "users"."email" AS "A_email", "users"."lowercase_email" AS "A_lowercase_email", "users"."display_name" AS "A_display_name", "users"."creation_date" AS "A_creation_date", "users"."password_hash" AS "A_password_hash", "users"."totp_secret" AS "A_totp_secret", "users"."mfa_type" AS "A_mfa_type", "users"."uuid" AS "A_uuid", "r1"."group_id" AS "B_group_id", "r1"."display_name" AS "B_display_name", "r1"."lowercase_display_name" AS "B_lowercase_display_name", "r1"."creation_date" AS "B_creation_date", "r1"."uuid" AS "B_uuid" FROM "users" LEFT JOIN "memberships" AS "r0" ON "users"."user_id" = "r0"."user_id" LEFT JOIN "groups" AS "r1" ON "r0"."group_id" = "r1"."group_id" WHERE $1 AND "users"."user_id" IN (SELECT "user_attributes"."user_attribute_user_id" FROM "user_attributes" WHERE "user_attributes"."user_attribute_name" = $2 AND "user_attributes"."user_attribute_value" = $3) ORDER BY "users"."user_id" ASC, "r1"."display_name" ASC
DETAIL: Parameters: $1 = 't', $2 = 'sambasid', $3 = '\x2e00000000000000732d312d352d32312d323239333236303632392d313235373134323137352d313930333736323138352d33323032'

We can see that it searches for an encoded attribute value of (note the 7 after the row of zeroes):

\x2e00000000000000732d312d352d32312d323239333236303632392d313235373134323137352d313930333736323138352d33323032

If we look it up in the user_attributes table we can see it's encoded differently (note the 5 after the row of zeroes):

user_attribute_user_id | user_attribute_name | user_attribute_value
------------------------+---------------------+----------------------------------------------------------------------------------------------------------------
example | sambasid | \x2e00000000000000532d312d352d32312d323239333236303632392d313235373134323137352d313930333736323138352d33323032

To summarize: Even if you explicitly search for a value with uppercase letters, the value will be lowercased, serialized and then used for lookup in the DB. If the desired attribute value is stored with uppercase letters - as you searched for - it will not be found, as it is stored in the original case, serialized (and the serialization is of course case sensitive).

Originally created by @broeng on GitHub (Jan 12, 2025). Original GitHub issue: https://github.com/lldap/lldap/issues/1073 **Describe the bug** When storing attribute values, the values are stored in the DB in the case they are given, serialized with serde. I think this is correct behaviour. When an LDAP search for a specific attribute value is done, the value in the search filter will have been lowercased, before it is serialized with Serde, and used for lookup in the user_attributes table. This means the serialized value will never match a value that was not also stored in lowercase. **To Reproduce** Steps to reproduce the behavior: 1. Create a new custom user attribute, a string value ('sambaSID' in this example). 2. Assign a value with uppercase letters to this attribute for a user. ('S-1-5-21-2293260629-1257142175-1903762185-3202' in this example). 3. Search for this attribute and value with ldapsearch, with same casing. `ldapsearch ... '(&(objectclass=person)(sambaSID=S-1-5-21-2293260629-1257142175-1903762185-3202))'` 4. Observe no results returned. **Expected behavior** Expected to get user back, with the given attribute and value. **Logs** Query from LLDAP - observe how value is lowercased in filter expression: > INFO LDAP request [ 1.07ms | 5.46% / 100.00% ] session_id: 557bc751-8cee-46f8-ac2a-6db7b5921e4e > DEBUG ┝━ 🐛 [debug]: | msg: LdapMsg { msgid: 2, op: SearchRequest(LdapSearchRequest { base: "dc=example,dc=com", scope: Subtree, aliases: Never, sizelimit: 0, timelimit: 0, typesonly: false, filter: And([Equality("objectclass", "person"), Equality("sambasid", "S-1-5-21-2293260629-1257142175-1903762185-3202")]), attrs: [] }), ctrl: [] } > DEBUG ┝━ do_search [ 1.01ms | 33.85% / 94.54% ] > DEBUG │ ┝━ 🐛 [debug]: | request.base: "dc=apc,dc=dk" | scope: Global > DEBUG │ ┝━ get_user_list [ 336µs | 1.92% / 31.48% ] > DEBUG │ │ ┝━ 🐛 [debug]: | filters: And([And([]), AttributeEquality(AttributeName(CaseInsensitiveString("sambasid")), Serialized("s-1-5-21-2293260629-1257142175-1903762185-3202"))]) > DEBUG │ │ ┕━ list_users [ 316µs | 29.55% ] filters: Some(And([And([]), AttributeEquality(AttributeName(CaseInsensitiveString("sambasid")), Serialized("s-1-5-21-2293260629-1257142175-1903762185-3202"))])) | _get_groups: false > DEBUG │ │ ┕━ 🐛 [debug]: | return: [] From postgres log: > LOG: execute sqlx_s_1: SELECT "users"."user_id" AS "A_user_id", "users"."email" AS "A_email", "users"."lowercase_email" AS "A_lowercase_email", "users"."display_name" AS "A_display_name", "users"."creation_date" AS "A_creation_date", "users"."password_hash" AS "A_password_hash", "users"."totp_secret" AS "A_totp_secret", "users"."mfa_type" AS "A_mfa_type", "users"."uuid" AS "A_uuid", "r1"."group_id" AS "B_group_id", "r1"."display_name" AS "B_display_name", "r1"."lowercase_display_name" AS "B_lowercase_display_name", "r1"."creation_date" AS "B_creation_date", "r1"."uuid" AS "B_uuid" FROM "users" LEFT JOIN "memberships" AS "r0" ON "users"."user_id" = "r0"."user_id" LEFT JOIN "groups" AS "r1" ON "r0"."group_id" = "r1"."group_id" WHERE $1 AND "users"."user_id" IN (SELECT "user_attributes"."user_attribute_user_id" FROM "user_attributes" WHERE "user_attributes"."user_attribute_name" = $2 AND "user_attributes"."user_attribute_value" = $3) ORDER BY "users"."user_id" ASC, "r1"."display_name" ASC > DETAIL: Parameters: $1 = 't', $2 = 'sambasid', $3 = '\x2e00000000000000732d312d352d32312d323239333236303632392d313235373134323137352d313930333736323138352d33323032' We can see that it searches for an encoded attribute value of (note the 7 after the row of zeroes): > \x2e00000000000000732d312d352d32312d323239333236303632392d313235373134323137352d313930333736323138352d33323032 If we look it up in the user_attributes table we can see it's encoded differently (note the 5 after the row of zeroes): > user_attribute_user_id | user_attribute_name | user_attribute_value > ------------------------+---------------------+---------------------------------------------------------------------------------------------------------------- > example | sambasid | \x2e00000000000000532d312d352d32312d323239333236303632392d313235373134323137352d313930333736323138352d33323032 To summarize: Even if you explicitly search for a value with uppercase letters, the value will be lowercased, serialized and then used for lookup in the DB. If the desired attribute value is stored with uppercase letters - as you searched for - it will not be found, as it is stored in the original case, serialized (and the serialization is of course case sensitive).
kerem 2026-02-27 08:17:00 +03:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@broeng commented on GitHub (Jan 22, 2025):

Fixed with PR #1074

<!-- gh-comment-id:2606880738 --> @broeng commented on GitHub (Jan 22, 2025): Fixed with PR #1074
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/lldap-lldap#386
No description provided.