From ecc3bcabd4a63bb7142cb739ca7a3901a74b8264 Mon Sep 17 00:00:00 2001 From: Piotr Smaron Date: Wed, 1 Apr 2026 15:13:45 +0200 Subject: [PATCH] test/ldap: add LDAP filter-injection reproducers Add tests that reproduce LDAP filter injection via unescaped {USER} substitution (SCYLLADB-1309). A wildcard username ('*') matches every group entry, and a parenthesis payload (")(uid=*") breaks the search filter. Extend the LDAP test fixture (ldap_server.py, slapd.conf) with memberUid attributes and the NIS schema so the new tests can exercise direct filter-value substitution. --- test/ldap/role_manager_test.cc | 40 ++++++++++++++++++++++++++++++++++ test/pylib/ldap_server.py | 7 ++++++ test/resource/slapd.conf | 2 ++ 3 files changed, 49 insertions(+) diff --git a/test/ldap/role_manager_test.cc b/test/ldap/role_manager_test.cc index 61ded11bc1..5475081009 100644 --- a/test/ldap/role_manager_test.cc +++ b/test/ldap/role_manager_test.cc @@ -277,6 +277,10 @@ const auto flaky_server_query_template = fmt::format( "ldap://localhost:{}/{}?cn?sub?(uniqueMember=uid={{USER}},ou=People,dc=example,dc=com)", std::stoi(ldap_port) + 2, base_dn); +const auto member_uid_query_template = fmt::format( + "ldap://localhost:{}/dc=example,dc=com?cn?sub?(memberUid={{USER}})", + ldap_port); + auto make_ldap_manager(cql_test_env& env, sstring query_template = default_query_template) { auto stop_role_manager = [] (auth::ldap_role_manager* m) { m->stop().get(); @@ -340,6 +344,42 @@ SEASTAR_TEST_CASE(ldap_wrong_role) { }); } +SEASTAR_TEST_CASE(ldap_filter_injection_with_wildcard_user) { + return do_with_cql_env_thread([](cql_test_env& env) { + auto m = make_ldap_manager(env, member_uid_query_template); + m->start().get(); + do_with_mc(env, [&] (::service::group0_batch& b) { + m->create("*", auth::role_config{.is_superuser = false, .can_login = true}, b).get(); + m->create("role1", auth::role_config{}, b).get(); + m->create("role2", auth::role_config{.is_superuser = true, .can_login = false}, b).get(); + }); + + // BUG: Without escaping, '*' is interpolated literally into the LDAP + // filter (memberUid=*), which matches all group entries. + const role_set expected{"*", "role1", "role2"}; + BOOST_REQUIRE_EQUAL(expected, m->query_granted("*", auth::recursive_role_query::no).get()); + }); +} + +SEASTAR_TEST_CASE(ldap_filter_injection_with_parenthesis_payload) { + return do_with_cql_env_thread([](cql_test_env& env) { + auto m = make_ldap_manager(env, member_uid_query_template); + m->start().get(); + + // BUG: Without escaping, the payload ")(uid=*" produces the malformed + // filter (memberUid=)(uid=*), which the LDAP server rejects outright. + const sstring payload = ")(uid=*"; + do_with_mc(env, [&] (::service::group0_batch& b) { + m->create(payload, auth::role_config{.is_superuser = false, .can_login = true}, b).get(); + m->create("role1", auth::role_config{}, b).get(); + m->create("role2", auth::role_config{.is_superuser = true, .can_login = false}, b).get(); + }); + + BOOST_REQUIRE_EXCEPTION(m->query_granted(payload, auth::recursive_role_query::no).get(), + std::runtime_error, exception_predicate::message_contains("Bad search filter")); + }); +} + SEASTAR_TEST_CASE(ldap_reconnect) { return do_with_cql_env_thread([](cql_test_env& env) { auto m = make_ldap_manager(env, flaky_server_query_template); diff --git a/test/pylib/ldap_server.py b/test/pylib/ldap_server.py index a70d1dd0b2..b1f196287b 100644 --- a/test/pylib/ldap_server.py +++ b/test/pylib/ldap_server.py @@ -60,17 +60,24 @@ userid: jdoe userPassword: pa55w0rd """, """dn: cn=role1,dc=example,dc=com objectClass: groupOfUniqueNames +objectClass: extensibleObject cn: role1 uniqueMember: uid=jsmith,ou=People,dc=example,dc=com uniqueMember: uid=cassandra,ou=People,dc=example,dc=com +memberUid: jsmith +memberUid: cassandra """, """dn: cn=role2,dc=example,dc=com objectClass: groupOfUniqueNames +objectClass: extensibleObject cn: role2 uniqueMember: uid=cassandra,ou=People,dc=example,dc=com +memberUid: cassandra """, """dn: cn=role3,dc=example,dc=com objectClass: groupOfUniqueNames +objectClass: extensibleObject cn: role3 uniqueMember: uid=jdoe,ou=People,dc=example,dc=com +memberUid: jdoe """, ] diff --git a/test/resource/slapd.conf b/test/resource/slapd.conf index 1d13610367..f6505dd43d 100644 --- a/test/resource/slapd.conf +++ b/test/resource/slapd.conf @@ -8,6 +8,8 @@ rootdn "cn=admin,cn=config" pidfile ./pidfile.pid include /etc/openldap/schema/core.schema +include /etc/openldap/schema/cosine.schema +include /etc/openldap/schema/nis.schema database mdb suffix "dc=example,dc=com"