Skip to content
Permalink
Browse files Browse the repository at this point in the history
Escape username and domain values to avoid SQL injection in string
concatenation (b/13399766, 2 of 2).

Code review: https://codereview.appspot.com/75770044
  • Loading branch information
jlacey@google.com committed Mar 14, 2014
1 parent 6c98659 commit 6fba04f
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 2 deletions.
Expand Up @@ -139,14 +139,15 @@ private String getUserName(ISessionManager sessionManager,
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("'");

Expand Down
Expand Up @@ -57,6 +57,56 @@ public static void appendObjectTypes(StringBuilder buffer,
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();
Expand Down
Expand Up @@ -369,6 +369,21 @@ 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;
Expand Down
@@ -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, '/'));
}
}

0 comments on commit 6fba04f

Please sign in to comment.