This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 75dba01b143 branch-4.1:[fix](ldap) Replace custom LDAP filter escaping
with `LdapEncoder.filterEncode` to prevent injection vulnerabilities and add
related documentation. (#61662) (#61774)
75dba01b143 is described below
commit 75dba01b1430c0023cc611335d2ae2a0125d4b0a
Author: seawinde <[email protected]>
AuthorDate: Fri Mar 27 09:44:30 2026 +0800
branch-4.1:[fix](ldap) Replace custom LDAP filter escaping with
`LdapEncoder.filterEncode` to prevent injection vulnerabilities and add related
documentation. (#61662) (#61774)
pr #61662
---
.../authentication/plugin/ldap/LdapClient.java | 7 ++--
.../authentication/plugin/ldap/LdapClientTest.java | 44 ++++++++++++++++++++++
.../doris/mysql/authenticate/ldap/LdapClient.java | 18 +++++----
.../mysql/authenticate/ldap/LdapClientTest.java | 30 +++++++++++++++
4 files changed, 88 insertions(+), 11 deletions(-)
diff --git
a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/main/java/org/apache/doris/authentication/plugin/ldap/LdapClient.java
b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/main/java/org/apache/doris/authentication/plugin/ldap/LdapClient.java
index 150ab55daca..176663f240d 100644
---
a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/main/java/org/apache/doris/authentication/plugin/ldap/LdapClient.java
+++
b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/main/java/org/apache/doris/authentication/plugin/ldap/LdapClient.java
@@ -26,6 +26,7 @@ import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.query.LdapQuery;
+import org.springframework.ldap.support.LdapEncoder;
import java.util.List;
import java.util.Map;
@@ -176,7 +177,7 @@ public class LdapClient {
if (!Strings.isNullOrEmpty(groupFilter)) {
// Support Open Directory implementations with custom filter
- String filter = groupFilter.replace("{login}", username);
+ String filter = groupFilter.replace("{login}",
LdapEncoder.filterEncode(username));
groupDns =
getDn(org.springframework.ldap.query.LdapQueryBuilder.query()
.attributes("dn")
.base(groupBaseDn)
@@ -256,8 +257,8 @@ public class LdapClient {
}
private String getUserFilter(String filterTemplate, String username) {
- // Replace {login} with actual username
- return filterTemplate.replace("{login}", username);
+ // Replace {login} with escaped username to prevent LDAP filter
injection (RFC 4515)
+ return filterTemplate.replace("{login}",
LdapEncoder.filterEncode(username));
}
private String requireConfig(Map<String, String> config, String key,
String description) {
diff --git
a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapClientTest.java
b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapClientTest.java
index 9e22c1896b3..7809795062c 100644
---
a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapClientTest.java
+++
b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapClientTest.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.springframework.ldap.support.LdapEncoder;
import java.util.HashMap;
import java.util.List;
@@ -262,4 +263,47 @@ class LdapClientTest {
Assertions.assertNotNull(groups);
Assertions.assertTrue(groups.isEmpty());
}
+
+ @Test
+ @DisplayName("UT-LDAP-C-018: LdapEncoder.filterEncode escapes RFC 4515
special characters")
+ void testLdapFilterEncoding() {
+ // Combined special characters
+ String input = "test*()\\\u0000";
+ String expected = "test\\2a\\28\\29\\5c\\00";
+ Assertions.assertEquals(expected, LdapEncoder.filterEncode(input));
+
+ // Null input
+ Assertions.assertNull(LdapEncoder.filterEncode(null));
+
+ // Normal username should not be altered
+ Assertions.assertEquals("zhangsan",
LdapEncoder.filterEncode("zhangsan"));
+ Assertions.assertEquals("[email protected]",
LdapEncoder.filterEncode("[email protected]"));
+
+ // Empty string
+ Assertions.assertEquals("", LdapEncoder.filterEncode(""));
+
+ // Each special character individually
+ Assertions.assertEquals("\\2a", LdapEncoder.filterEncode("*"));
+ Assertions.assertEquals("\\28", LdapEncoder.filterEncode("("));
+ Assertions.assertEquals("\\29", LdapEncoder.filterEncode(")"));
+ Assertions.assertEquals("\\5c", LdapEncoder.filterEncode("\\"));
+ Assertions.assertEquals("\\00", LdapEncoder.filterEncode("\u0000"));
+ }
+
+ @Test
+ @DisplayName("UT-LDAP-C-019: LdapEncoder.filterEncode blocks injection
payload")
+ void testFilterEncodeBlocksInjectionPayload() {
+ // Classic LDAP injection: dorisuser6)(mail=testp*
+ String malicious = "dorisuser6)(mail=testp*";
+ String escaped = LdapEncoder.filterEncode(malicious);
+ Assertions.assertEquals("dorisuser6\\29\\28mail=testp\\2a", escaped);
+
+ // The escaped value should be safe for filter template substitution
+ String filter = "(uid={login})".replace("{login}", escaped);
+ Assertions.assertEquals("(uid=dorisuser6\\29\\28mail=testp\\2a)",
filter);
+ // No unescaped parentheses or wildcards from input
+ Assertions.assertFalse(escaped.contains("("));
+ Assertions.assertFalse(escaped.contains(")"));
+ Assertions.assertFalse(escaped.contains("*"));
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
index 79248ab0212..5d03917f0d3 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
@@ -25,7 +25,6 @@ import org.apache.doris.common.util.NetUtils;
import org.apache.doris.common.util.SymmetricEncryption;
import org.apache.doris.persist.LdapInfo;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
@@ -37,6 +36,7 @@ import
org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.pool.factory.PoolingContextSource;
import org.springframework.ldap.pool.validation.DefaultDirContextValidator;
import org.springframework.ldap.query.LdapQuery;
+import org.springframework.ldap.support.LdapEncoder;
import
org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;
import java.util.List;
@@ -146,7 +146,7 @@ public class LdapClient {
try {
clientInfo.getLdapTemplateNoPool().authenticate(org.springframework.ldap.query.LdapQueryBuilder.query()
.base(LdapConfig.ldap_user_basedn)
- .filter(getUserFilter(LdapConfig.ldap_user_filter,
userName)), password);
+ .filter(applyLoginFilter(LdapConfig.ldap_user_filter,
userName)), password);
return true;
} catch (Exception e) {
LOG.info("ldap client checkPassword failed, userName: {}",
userName, e);
@@ -167,7 +167,7 @@ public class LdapClient {
List<String> groupDns;
if (!LdapConfig.ldap_group_filter.isEmpty()) {
// Support Open Directory implementations
- String filter = LdapConfig.ldap_group_filter.replace("{login}",
userName);
+ String filter = applyLoginFilter(LdapConfig.ldap_group_filter,
userName);
groupDns =
getDn(org.springframework.ldap.query.LdapQueryBuilder.query()
.attributes("dn")
.base(LdapConfig.ldap_group_basedn)
@@ -195,13 +195,13 @@ public class LdapClient {
private String getUserDn(String userName) {
List<String> userDns =
getDn(org.springframework.ldap.query.LdapQueryBuilder.query()
-
.base(LdapConfig.ldap_user_basedn).filter(getUserFilter(LdapConfig.ldap_user_filter,
userName)));
+
.base(LdapConfig.ldap_user_basedn).filter(applyLoginFilter(LdapConfig.ldap_user_filter,
userName)));
if (userDns == null || userDns.isEmpty()) {
return null;
}
if (userDns.size() > 1) {
String msg = String.format("[%s] not unique in LDAP server: [%s]",
- getUserFilter(LdapConfig.ldap_user_filter, userName),
userDns);
+ applyLoginFilter(LdapConfig.ldap_user_filter, userName),
userDns);
LOG.error(msg);
ErrorReport.report(ErrorCode.ERROR_LDAP_USER_NOT_UNIQUE_ERR,
userName);
throw new RuntimeException(msg);
@@ -209,7 +209,6 @@ public class LdapClient {
return userDns.get(0);
}
- @VisibleForTesting
public List<String> getDn(LdapQuery query) {
init();
try {
@@ -229,7 +228,10 @@ public class LdapClient {
}
}
- private String getUserFilter(String userFilter, String userName) {
- return userFilter.replaceAll("\\{login}", userName);
+ private String applyLoginFilter(String filter, String userName) {
+ if (filter == null) {
+ return null;
+ }
+ return filter.replace("{login}", LdapEncoder.filterEncode(userName));
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
index c0d6c36f83b..7790816856f 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
@@ -28,6 +28,7 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ldap.query.LdapQuery;
+import org.springframework.ldap.support.LdapEncoder;
import java.util.Arrays;
import java.util.List;
@@ -118,6 +119,35 @@ public class LdapClientTest {
secureUrl.startsWith("ldaps://"));
}
+ @Test
+ public void testLdapFilterEncoding() {
+ // Combined special characters
+ String input = "test*()\\\u0000";
+ String expected = "test\\2a\\28\\29\\5c\\00";
+ Assert.assertEquals(expected, LdapEncoder.filterEncode(input));
+
+ // Null input
+ Assert.assertNull(LdapEncoder.filterEncode(null));
+
+ // Normal username should not be altered
+ Assert.assertEquals("zhangsan", LdapEncoder.filterEncode("zhangsan"));
+ Assert.assertEquals("[email protected]",
LdapEncoder.filterEncode("[email protected]"));
+
+ // Empty string
+ Assert.assertEquals("", LdapEncoder.filterEncode(""));
+
+ // Each special character individually
+ Assert.assertEquals("\\2a", LdapEncoder.filterEncode("*"));
+ Assert.assertEquals("\\28", LdapEncoder.filterEncode("("));
+ Assert.assertEquals("\\29", LdapEncoder.filterEncode(")"));
+ Assert.assertEquals("\\5c", LdapEncoder.filterEncode("\\"));
+ Assert.assertEquals("\\00", LdapEncoder.filterEncode("\u0000"));
+
+ // Injection payload: dorisuser6)(mail=testp*
+ Assert.assertEquals("dorisuser6\\29\\28mail=testp\\2a",
+ LdapEncoder.filterEncode("dorisuser6)(mail=testp*"));
+ }
+
@After
public void tearDown() {
LdapConfig.ldap_use_ssl = false; // restoring default value for other
tests
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]