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]

Reply via email to