CVE-2014-125083: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A vulnerability has been found in Anant Labs google-enterprise-connector-dctm up to 3.2.3 and classified as critical. Affected by this vulnerability is an unknown functionality. The manipulation of the argument username/domain leads to sql injection. The patch is named 6fba04f18ab7764002a1da308e7cd9712b501cb7. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-218911.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2023-01-19T10:15Z
Repository: https://github.com/AnantLabs/google-enterprise-connector-dctm
Language: Java
Files changed: projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DctmAuthenticationManager.java, projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DqlUtils.java, projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DctmMockAuthenticationManagerTest.java, projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DqlUtilsTest.java
Commit date: 2014-03-14 08:29:27 +0000
Commit message: Escape username and domain values to avoid SQL injection in string
concatenation (b/13399766, 2 of 2).

Code review: https://codereview.appspot.com/75770044

Diff: diff --git a/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DctmAuthenticationManager.java b/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DctmAuthenticationManager.java
index 7f2f133..d8b1647 100644
--- a/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DctmAuthenticationManager.java
+++ b/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DctmAuthenticationManager.java
@@ -137,18 +137,19 @@ public class DctmAuthenticationManager implements AuthenticationManager {
     ISession session = sessionManager.getSession(docbase);
     try {
       StringBuilder queryBuff = new StringBuilder();
       queryBuff.append("select user_name, user_ldap_dn from ");
       queryBuff.append("dm_user where user_login_name = '");
-      queryBuff.append(userLoginName);
+      queryBuff.append(DqlUtils.escapeString(userLoginName));
       if (!domainName.isEmpty()) {
         queryBuff.append("' and user_source = 'LDAP'");
         queryBuff.append(" and LOWER(user_ldap_dn) like '%,");
-        queryBuff.append(domainName);
+        queryBuff.append(DqlUtils.escapePattern(domainName.toString(), '\\'));
         if (domainName.size() == 1) { // NetBIOS domain
           queryBuff.append(",%");
         }
+        queryBuff.append("' escape '\\");
       }
       queryBuff.append("'");
 
       IQuery query = clientX.getQuery();
       query.setDQL(queryBuff.toString());
diff --git a/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DqlUtils.java b/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DqlUtils.java
index 030570c..cee4bec 100644
--- a/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DqlUtils.java
+++ b/projects/dctm-core/source/java/com/google/enterprise/connector/dctm/DqlUtils.java
@@ -55,10 +55,60 @@ public class DqlUtils {
       buffer.append(" OR r_object_type='").append(name).append("'");
     }
     buffer.append(')');
   }
 
+  /**
+   * Escapes single quotes (') in the given value by doubling them,
+   * as per SQL.
+   */
+  public static String escapeString(String value) {
+    StringBuilder buffer = new StringBuilder(value.length() + 2);
+    for (int i = 0; i < value.length(); i++) {
+      char c = value.charAt(i);
+      switch (c) {
+        case '\'':
+          buffer.append("''");
+          break;
+        default:
+          buffer.append(c);
+          break;
+      }
+    }
+    return buffer.toString();
+  }
+
+  /**
+   * Escapes single quotes (') in the given value by doubling them, as
+   * per SQL, and escapes the LIKE wildcards, percent (%) and
+   * underscore (_), using the given escape character, as well as
+   * escaping the escape character itself.
+   */
+  public static String escapePattern(String value, char escapeChar) {
+    StringBuilder buffer = new StringBuilder(value.length() + 2);
+    for (int i = 0; i < value.length(); i++) {
+      char c = value.charAt(i);
+      switch (c) {
+        case '\'':
+          buffer.append("''");
+          break;
+        case '%':
+        case '_':
+          buffer.append(escapeChar);
+          buffer.append(c);
+          break;
+        default:
+          if (c == escapeChar) {
+            buffer.append(escapeChar);
+          }
+          buffer.append(c);
+          break;
+      }
+    }
+    return buffer.toString();
+  }
+
   /** This class should not be instantiated. */
   private DqlUtils() {
     throw new AssertionError();
   }
 }
diff --git a/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DctmMockAuthenticationManagerTest.java b/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DctmMockAuthenticationManagerTest.java
index 01c815d..6bb4697 100644
--- a/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DctmMockAuthenticationManagerTest.java
+++ b/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DctmMockAuthenticationManagerTest.java
@@ -367,10 +367,25 @@ public class DctmMockAuthenticationManagerTest extends TestCase {
 
   public void testDomain_dnsParent() throws Exception {
     testParentDomainFail("ldapuser", "example.com");
   }
 
+  public void testSqlInjection_userNoDomain() throws Exception {
+    testDomainFail("' or user_name = 'localuser", "");
+  }
+
+  public void testSqlInjection_userDomain() throws Exception {
+    testDomainFail(
+        "ldapuser' and user_ldap_dn like '%dc=ajax,dc=example,dc=com,dc=au' --",
+        "ajax.example.com");
+  }
+
+  /** The post-processing using LdapName also blocks this. */
+  public void testSqlInjection_domain() throws Exception {
+    testDomainFail("ldapuser", "acme.example%");
+  }
+
   private Collection<String> toStrings(Collection<?> groups) {
     if (groups == null) {
       return null;
     }
     Collection<String> names = new ArrayList<String>(groups.size());
diff --git a/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DqlUtilsTest.java b/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DqlUtilsTest.java
new file mode 100644
index 0000000..426e5cf
--- /dev/null
+++ b/projects/dctm-core/source/javatests/com/google/enterprise/connector/dctm/DqlUtilsTest.java
@@ -0,0 +1,81 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// 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 com.google.enterprise.connector.dctm;
+
+import junit.framework.TestCase;
+
+public class DqlUtilsTest extends TestCase {
+  public void testEscapeString_null() {
+    try {
+      DqlUtils.escapeString(null);
+      fail("Expected a NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testEscapeString_empty() {
+    assertEquals("", DqlUtils.escapeString(""));
+  }
+
+  public void testEscapeString_unescapedString() {
+    String value = "hello world";
+    assertEquals(value, DqlUtils.escapeString(value));
+  }
+
+  public void testEscapeString_unescapedPattern() {
+    String value = "\\ % _ \\\\ \\% \\_";
+    assertEquals(value, DqlUtils.escapeString(value));
+  }
+
+  public void testEscapeString_quoted() {
+    String value = "' \\ % _ \\' \\\\ \\% \\_";
+    assertEquals(value.replace("'", "''"), DqlUtils.escapeString(value));
+  }
+
+  public void testEscapePattern_null() {
+    try {
+      DqlUtils.escapePattern(null, '\\');
+      fail("Expected a NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testEscapePattern_empty() {
+    assertEquals("", DqlUtils.escapePattern("", '\\'));
+  }
+
+  public void testEscapePattern_unescapedString() {
+    String value = "hello world";
+    assertEquals(value, DqlUtils.escapePattern(value, '\\'));
+  }
+
+  public void testEscapePattern_escapedPattern() {
+    String value = "\\ % _ \\\\ \\% \\_";
+    String escaped = "\\\\ \\% \\_ \\\\\\\\ \\\\\\% \\\\\\_";
+    assertEquals(escaped, DqlUtils.escapePattern(value, '\\'));
+  }
+
+  public void testEscapePattern_quoted() {
+    String value = "' \\ % _ \\' \\\\ \\% \\_";
+    String escaped = "'' \\\\ \\% \\_ \\\\'' \\\\\\\\ \\\\\\% \\\\\\_";
+    assertEquals(escaped, DqlUtils.escapePattern(value, '\\'));
+  }
+
+  public void testEscapePattern_quotedSlash() {
+    String value = "' \\ % _ \\' \\\\ \\% \\_";
+    String escaped = "'' /\\ /% /_ /\\'' /\\/\\ /\\/% /\\/_";
+    assertEquals(escaped, DqlUtils.escapePattern(value, '/'));
+  }
+}

Vulnerable code:       queryBuff.append(userLoginName);
        queryBuff.append(domainName);
Fixed code:       queryBuff.append(DqlUtils.escapeString(userLoginName));
        queryBuff.append(DqlUtils.escapePattern(domainName.toString(), '\\'));
        queryBuff.append("' escape '\\");
  /**
   * Escapes single quotes (') in the given value by doubling them,
   * as per SQL.
   */
  public static String escapeString(String value) {
    StringBuilder buffer = new StringBuilder(value.length() + 2);
    for (int i = 0; i < value.length(); i++) {
      char c = value.charAt(i);
      switch (c) {
        case '\'':
          buffer.append("''");
          break;
        default:
          buffer.append(c);
          break;
      }
    }
    return buffer.toString();
  }

  /**
   * Escapes single quotes (') in the given value by doubling them, as
   * per SQL, and escapes the LIKE wildcards, percent (%) and
   * underscore (_), using the given escape character, as well as
   * escaping the escape character itself.
   */
  public static String escapePattern(String value, char escapeChar) {
    StringBuilder buffer = new StringBuilder(value.length() + 2);
    for (int i = 0; i < value.length(); i++) {
      char c = value.charAt(i);
      switch (c) {
        case '\'':
          buffer.append("''");
          break;
        case '%':
        case '_':
          buffer.append(escapeChar);
          buffer.append(c);
          break;
        default:
          if (c == escapeChar) {
            buffer.append(escapeChar);
          }
          buffer.append(c);
          break;
      }
    }
    return buffer.toString();
  }

  public void testSqlInjection_userNoDomain() throws Exception {
    testDomainFail("' or user_name = 'localuser", "");
  }

  public void testSqlInjection_userDomain() throws Exception {
    testDomainFail(
        "ldapuser' and user_ldap_dn like '%dc=ajax,dc=example,dc=com,dc=au' --",
        "ajax.example.com");
  }

  /** The post-processing using LdapName also blocks this. */
  public void testSqlInjection_domain() throws Exception {
    testDomainFail("ldapuser", "acme.example%");
  }

// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 com.google.enterprise.connector.dctm;

import junit.framework.TestCase;

public class DqlUtilsTest extends TestCase {
  public void testEscapeString_null() {
    try {
      DqlUtils.escapeString(null);
      fail("Expected a NullPointerException");
    } catch (NullPointerException expected) {
    }
  }

  public void testEscapeString_empty() {
    assertEquals("", DqlUtils.escapeString(""));
  }

  public void testEscapeString_unescapedString() {
    String value = "hello world";
    assertEquals(value, DqlUtils.escapeString(value));
  }

  public void testEscapeString_unescapedPattern() {
    String value = "\\ % _ \\\\ \\% \\_";
    assertEquals(value, DqlUtils.escapeString(value));
  }

  public void testEscapeString_quoted() {
    String value = "' \\ % _ \\' \\\\ \\% \\_";
    assertEquals(value.replace("'", "''"), DqlUtils.escapeString(value));
  }

  public void testEscapePattern_null() {
    try {
      DqlUtils.escapePattern(null, '\\');
      fail("Expected a NullPointerException");
    } catch (NullPointerException expected) {
    }
  }

  public void testEscapePattern_empty() {
    assertEquals("", DqlUtils.escapePattern("", '\\'));
  }

  public void testEscapePattern_unescapedString() {
    String value = "hello world";
    assertEquals(value, DqlUtils.escapePattern(value, '\\'));
  }

  public void testEscapePattern_escapedPattern() {
    String value = "\\ % _ \\\\ \\% \\_";
    String escaped = "\\\\ \\% \\_ \\\\\\\\ \\\\\\% \\\\\\_";
    assertEquals(escaped, DqlUtils.escapePattern(value, '\\'));
  }

  public void testEscapePattern_quoted() {
    String value = "' \\ % _ \\' \\\\ \\% \\_";
    String escaped = "'' \\\\ \\% \\_ \\\\'' \\\\\\\\ \\\\\\% \\\\\\_";
    assertEquals(escaped, DqlUtils.escapePattern(value, '\\'));
  }

  public void testEscapePattern_quotedSlash() {
    String value = "' \\ % _ \\' \\\\ \\% \\_";
    String escaped = "'' /\\ /% /_ /\\'' /\\/\\ /\\/% /\\/_";
    assertEquals(escaped, DqlUtils.escapePattern(value, '/'));
  }
}