Skip to content

Commit f854cbc

Browse files
authored
Add LDAP filter escaping for special characters in search filters (#39567)
2 parents 660d2f6 + d7cf98e commit f854cbc

3 files changed

Lines changed: 83 additions & 6 deletions

File tree

ydb/core/security/ldap_auth_provider/ldap_utils.cpp

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,54 @@
55

66
namespace NKikimr {
77

8+
namespace {
9+
10+
// Escape LDAP filter value according to https://www.rfc-editor.org/rfc/rfc2254#page-5
11+
TString EscapeLdapFilterValue(TStringBuf value) {
12+
TStringBuilder escaped;
13+
escaped.reserve(value.size() + 4);
14+
for (unsigned char ch : value) {
15+
switch (ch) {
16+
case '*':
17+
escaped << "\\2a";
18+
break;
19+
case '(':
20+
escaped << "\\28";
21+
break;
22+
case ')':
23+
escaped << "\\29";
24+
break;
25+
case '\\':
26+
escaped << "\\5c";
27+
break;
28+
case '\0':
29+
escaped << "\\00";
30+
break;
31+
default:
32+
escaped << static_cast<char>(ch);
33+
break;
34+
}
35+
}
36+
return TString{escaped};
37+
}
38+
39+
} // namespace
40+
841
TSearchFilterCreator::TSearchFilterCreator(const NKikimrProto::TLdapAuthentication& settings)
942
: Settings(settings)
1043
{}
1144

1245
TString TSearchFilterCreator::GetFilter(const TString& userName) const {
46+
const TString escapedUserName = EscapeLdapFilterValue(userName);
1347
if (!Settings.GetSearchFilter().empty()) {
14-
return GetFormatSearchFilter(userName);
48+
return GetFormatSearchFilter(escapedUserName);
1549
} else if (!Settings.GetSearchAttribute().empty()) {
16-
return Settings.GetSearchAttribute() + "=" + userName;
50+
return Settings.GetSearchAttribute() + "=" + escapedUserName;
1751
}
18-
return "uid=" + userName;
52+
return "uid=" + escapedUserName;
1953
}
2054

21-
TString TSearchFilterCreator::GetFormatSearchFilter(const TString& userName) const {
55+
TString TSearchFilterCreator::GetFormatSearchFilter(const TString& escapedUserName) const {
2256
const TStringBuf namePlaceHolder = "$username";
2357
const TString& searchFilter = Settings.GetSearchFilter();
2458
size_t n = searchFilter.find(namePlaceHolder);
@@ -28,7 +62,7 @@ TString TSearchFilterCreator::GetFormatSearchFilter(const TString& userName) con
2862
TStringStream result;
2963
size_t pos = 0;
3064
while (n != TString::npos) {
31-
result << searchFilter.substr(pos, n - pos) << userName;
65+
result << searchFilter.substr(pos, n - pos) << escapedUserName;
3266
pos = n + namePlaceHolder.size();
3367
n = searchFilter.find(namePlaceHolder, pos);
3468
}

ydb/core/security/ldap_auth_provider/ldap_utils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class TSearchFilterCreator {
1010
TString GetFilter(const TString& userName) const;
1111

1212
private:
13-
TString GetFormatSearchFilter(const TString& userName) const;
13+
// Must already be RFC 2254 filter-escaped for embedding in a filter
14+
TString GetFormatSearchFilter(const TString& escapedUserName) const;
1415

1516
private:
1617
const NKikimrProto::TLdapAuthentication& Settings;

ydb/core/security/ldap_auth_provider/ldap_utils_ut.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <library/cpp/testing/unittest/registar.h>
2+
#include <util/generic/strbuf.h>
23
#include "ldap_utils.h"
34

45
namespace NKikimr {
@@ -60,6 +61,47 @@ Y_UNIT_TEST_SUITE(TLdapUtilsSearchFilterCreatorTest) {
6061
const TString filter = filterCreator.GetFilter(login);
6162
UNIT_ASSERT_STRINGS_EQUAL(expectedFilter, filter);
6263
}
64+
65+
Y_UNIT_TEST(EscapeSpecialCharsInDefaultUidFilter) {
66+
NKikimrProto::TLdapAuthentication settings;
67+
TSearchFilterCreator filterCreator(settings);
68+
static constexpr char LOGIN_BYTES[] = {'u', '*', '(', ')', '\\', '\0', 'x'};
69+
const TString login(TStringBuf(LOGIN_BYTES, sizeof(LOGIN_BYTES)));
70+
const TString filter = filterCreator.GetFilter(login);
71+
UNIT_ASSERT_STRINGS_EQUAL(R"(uid=u\2a\28\29\5c\00x)", filter);
72+
}
73+
74+
Y_UNIT_TEST(EscapeSpecialCharsInSearchAttributeFilter) {
75+
NKikimrProto::TLdapAuthentication settings;
76+
settings.SetSearchAttribute("mail");
77+
TSearchFilterCreator filterCreator(settings);
78+
const TString login("a)admin");
79+
const TString filter = filterCreator.GetFilter(login);
80+
UNIT_ASSERT_STRINGS_EQUAL(R"(mail=a\29admin)", filter);
81+
}
82+
83+
Y_UNIT_TEST(EscapeInjectionInTemplatePlaceholder) {
84+
NKikimrProto::TLdapAuthentication settings;
85+
settings.SetSearchFilter("(&(uid=$username)(objectClass=person))");
86+
TSearchFilterCreator filterCreator(settings);
87+
const TString login(")(|(uid=*");
88+
const TString filter = filterCreator.GetFilter(login);
89+
UNIT_ASSERT_STRINGS_EQUAL("(&(uid=\\29\\28|\\28uid=\\2a)(objectClass=person))", filter);
90+
}
91+
92+
Y_UNIT_TEST(EscapeSpecialCharsWithTwoUsernamePlaceholders) {
93+
auto getFilterString = [](const TString& name) {
94+
return "|(&(uid=" + name + ")(groupid=1234))(&(login=" + name + ")(groupid=9876))";
95+
};
96+
NKikimrProto::TLdapAuthentication settings;
97+
settings.SetSearchFilter(getFilterString("$username"));
98+
TSearchFilterCreator filterCreator(settings);
99+
const TString login(")(|(uid=*");
100+
const TString filter = filterCreator.GetFilter(login);
101+
const TString escaped = "\\29\\28|\\28uid=\\2a";
102+
const TString expected = "|(&(uid=" + escaped + ")(groupid=1234))(&(login=" + escaped + ")(groupid=9876))";
103+
UNIT_ASSERT_STRINGS_EQUAL(expected, filter);
104+
}
63105
}
64106

65107
Y_UNIT_TEST_SUITE(TLdapUtilsUrisCreatorTest) {

0 commit comments

Comments
 (0)