Repository: trinodb/trino (Java) — Security

Issue:
LDAP injection attacks are possible through user field
LDAP password authenticator doesn't seem to escape special LDAP characters, which makes it possible to change the logic of LDAP searches and binds.

See [Testing for LDAP Injection](https://www.owasp.org/index.php/Testing_for_LDAP_Injection_(OTG-INPVAL-006)) or [Understanding and Exploiting Web-based LDAP](https://pen-testing.sans.org/blog/2017/11/27/understanding-and-exploiting-web-based-ldap) for examples and [LDAP Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html) for possible fixes.


Discussion:

Resolving patch:
diff --git a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/ldap/LdapAuthenticator.java b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/ldap/LdapAuthenticator.java
index e0dbd1c9d2dc..e1d3c98cd282 100644
--- a/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/ldap/LdapAuthenticator.java
+++ b/presto-password-authenticators/src/main/java/io/prestosql/plugin/password/ldap/LdapAuthenticator.java
@@ -13,6 +13,8 @@
  */
 package io.prestosql.plugin.password.ldap;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
 import com.google.common.base.VerifyException;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
@@ -55,6 +57,8 @@ public class LdapAuthenticator
         implements PasswordAuthenticator
 {
     private static final Logger log = Logger.get(LdapAuthenticator.class);
+    private static final CharMatcher SPECIAL_CHARACTERS = CharMatcher.anyOf(",=+<>#;*()\"\\\u0000");
+    private static final CharMatcher WHITESPACE = CharMatcher.anyOf(" \r");
 
     private final String userBindSearchPattern;
     private final Optional<String> groupAuthorizationSearchPattern;
@@ -102,6 +106,9 @@ private Principal authenticate(Credentials credentials)
 
     private Principal authenticate(String user, String password)
     {
+        if (containsSpecialCharacters(user)) {
+            throw new AccessDeniedException("Username contains a special LDAP character");
+        }
         Map<String, String> environment = createEnvironment(user, password);
         DirContext context = null;
         try {
@@ -126,6 +133,22 @@ private Principal authenticate(String user, String password)
         }
     }
 
+    /**
+     * Returns {@code true} when parameter contains a character that has a special meaning in
+     * LDAP search or bind name (DN).
+     *
+     * Based on <a href="https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java">Preventing_LDAP_Injection_in_Java</a> and
+     * {@link javax.naming.ldap.Rdn#escapeValue(Object) escapeValue} method.
+     */
+    @VisibleForTesting
+    static boolean containsSpecialCharacters(String user)
+    {
+        if (WHITESPACE.indexIn(user) == 0 || WHITESPACE.lastIndexIn(user) == user.length() - 1) {
+            return true;
+        }
+        return SPECIAL_CHARACTERS.matchesAnyOf(user);
+    }
+
     private Map<String, String> createEnvironment(String user, String password)
     {
         return ImmutableMap.<String, String>builder()
@@ -173,7 +196,7 @@ private void checkForGroupMembership(String user, DirContext context)
 
     private static String replaceUser(String pattern, String user)
     {
-        return pattern.replaceAll("\\$\\{USER}", user);
+        return pattern.replace("${USER}", user);
     }
 
     private static void closeContext(DirContext context)


Test patch:
diff --git a/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/ldap/TestLdapAuthenticator.java b/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/ldap/TestLdapAuthenticator.java
new file mode 100644
index 000000000000..3ee094f33b88
--- /dev/null
+++ b/presto-password-authenticators/src/test/java/io/prestosql/plugin/password/ldap/TestLdapAuthenticator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.prestosql.plugin.password.ldap;
+
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestLdapAuthenticator
+{
+    @Test
+    public void testContainsSpecialCharacters()
+    {
+        assertThat(LdapAuthenticator.containsSpecialCharacters("The quick brown fox jumped over the lazy dogs"))
+                .as("English pangram")
+                .isEqualTo(false);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("Pchnąć w tę łódź jeża lub ośm skrzyń fig"))
+                .as("Perfect polish pangram")
+                .isEqualTo(false);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす（ん）"))
+                .as("Japanese hiragana pangram - Iroha")
+                .isEqualTo(false);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("*"))
+                .as("LDAP wildcard")
+                .isEqualTo(true);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("   John Doe"))
+                .as("Beginning with whitespace")
+                .isEqualTo(true);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("John Doe  \r"))
+                .as("Ending with whitespace")
+                .isEqualTo(true);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("Hi (This) = is * a \\ test # ç à ô"))
+                .as("Multiple special characters")
+                .isEqualTo(true);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("John\u0000Doe"))
+                .as("NULL character")
+                .isEqualTo(true);
+        assertThat(LdapAuthenticator.containsSpecialCharacters("John Doe <john.doe@company.com>"))
+                .as("Angle brackets")
+                .isEqualTo(true);
+    }
+}