CVE-2021-32651: Improper Neutralization of Special Elements used in an LDAP Query ('LDAP Injection') (CWE-90)
value: OneDev is a development operations platform. If the LDAP external authentication mechanism is enabled in OneDev versions 4.4.1 and prior, an attacker can manipulate a user search filter to send forged queries to the application and explore the LDAP tree using Blind LDAP Injection techniques. The specific payload depends on how the User Search Filter property is configured in OneDev. This issue was fixed in version 4.4.2.
CWE description: The product constructs all or part of an LDAP query using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended LDAP query when it is sent to a downstream component.
CVSS v3 base score: 4.3
Published: 2021-06-01T18:15Z
Repository: https://github.com/theonedev/onedev
Language: Java
Files changed: server-plugin/server-plugin-authenticator-ldap/src/main/java/io/onedev/server/plugin/authenticator/ldap/LdapAuthenticator.java
Commit date: 2021-05-29 10:41:52 +0800
Commit message: Fix issue #304 - Potential information leak via Ldap injection when ldap
authenticator is enabled
Diff: diff --git a/server-plugin/server-plugin-authenticator-ldap/src/main/java/io/onedev/server/plugin/authenticator/ldap/LdapAuthenticator.java b/server-plugin/server-plugin-authenticator-ldap/src/main/java/io/onedev/server/plugin/authenticator/ldap/LdapAuthenticator.java
index 2d005e8734f..2cf4963b695 100644
--- a/server-plugin/server-plugin-authenticator-ldap/src/main/java/io/onedev/server/plugin/authenticator/ldap/LdapAuthenticator.java
+++ b/server-plugin/server-plugin-authenticator-ldap/src/main/java/io/onedev/server/plugin/authenticator/ldap/LdapAuthenticator.java
@@ -179,11 +179,13 @@ public class LdapAuthenticator extends Authenticator {
 		try {
 			userSearchBase = new CompositeName().add(getUserSearchBase());
 		} catch (InvalidNameException e) {
 			throw new RuntimeException(e);
 		}
-        String userSearchFilter = StringUtils.replace(getUserSearchFilter(), "{0}", token.getUsername());
+		
+        String userSearchFilter = StringUtils.replace(getUserSearchFilter(), "{0}", 
+        		escape(token.getUsername()));
         userSearchFilter = StringUtils.replace(userSearchFilter, "\\", "\\\\");
         logger.debug("Evaluated user search filter: " + userSearchFilter);
         
         SearchControls searchControls = new SearchControls();
         searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
@@ -435,6 +437,48 @@ public class LdapAuthenticator extends Authenticator {
 	@Override
 	public boolean isManagingSshKeys() {
 		return getUserSshKeyAttribute() != null;
 	}
 	
+	/* Copied from Spring LdapEncoder.java */
+    private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
+
+    static {
+
+        // Filter encoding table -------------------------------------
+
+        // fill with char itself
+        for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
+            FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
+        }
+
+        // escapes (RFC2254)
+        FILTER_ESCAPE_TABLE['*'] = "\\2a";
+        FILTER_ESCAPE_TABLE['('] = "\\28";
+        FILTER_ESCAPE_TABLE[')'] = "\\29";
+        FILTER_ESCAPE_TABLE['\\'] = "\\5c";
+        FILTER_ESCAPE_TABLE[0] = "\\00";
+
+    }
+
+    private static String escape(String value) {
+        // make buffer roomy
+        StringBuilder encodedValue = new StringBuilder(value.length() * 2);
+
+        int length = value.length();
+
+        for (int i = 0; i < length; i++) {
+
+            char c = value.charAt(i);
+
+            if (c < FILTER_ESCAPE_TABLE.length) {
+                encodedValue.append(FILTER_ESCAPE_TABLE[c]);
+            } else {
+                // default: add the char
+                encodedValue.append(c);
+            }
+        }
+
+        return encodedValue.toString();
+    }	
+	
 }

Vulnerable code:         String userSearchFilter = StringUtils.replace(getUserSearchFilter(), "{0}", token.getUsername());
Fixed code: 		
        String userSearchFilter = StringUtils.replace(getUserSearchFilter(), "{0}", 
        		escape(token.getUsername()));
	/* Copied from Spring LdapEncoder.java */
    private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];

    static {

        // Filter encoding table -------------------------------------

        // fill with char itself
        for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
            FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
        }

        // escapes (RFC2254)
        FILTER_ESCAPE_TABLE['*'] = "\\2a";
        FILTER_ESCAPE_TABLE['('] = "\\28";
        FILTER_ESCAPE_TABLE[')'] = "\\29";
        FILTER_ESCAPE_TABLE['\\'] = "\\5c";
        FILTER_ESCAPE_TABLE[0] = "\\00";

    }

    private static String escape(String value) {
        // make buffer roomy
        StringBuilder encodedValue = new StringBuilder(value.length() * 2);

        int length = value.length();

        for (int i = 0; i < length; i++) {

            char c = value.charAt(i);

            if (c < FILTER_ESCAPE_TABLE.length) {
                encodedValue.append(FILTER_ESCAPE_TABLE[c]);
            } else {
                // default: add the char
                encodedValue.append(c);
            }
        }

        return encodedValue.toString();
    }