To prevent LDAP injection, it is always recommended to enforce stringent input validation functions before processing data for LDAP persistence. In the case of an application that relies on client-side data validation, it becomes important to re-verify and validate them on the server side as well. The data validation should verify the input in terms of required LDAP attributes and its known data type, locale, meta characters, format, length, legal values, etc. To prevent issues with insecure LDAP configuration and access control policies, it is often recommended to verify LDAP configuration and enforce principle of least privilege and role-based access control (RBAC) policies.
LDAP injection is also very similar to the SQL injection vulnerability with relational databases (RDBMS). Using RDBMS is not a viable alternative to LDAP. LDAP is a directory protocol commonly used to represent organizational structure and its users as a hierarchy of objects. The hierarchical representation of LDAP information is one of the biggest advantages over RDBMS that helps implement faster lookup, query and delegation of responsibilities based on an organizational structure, sub-organization, location, users, groups, roles and access-control policies.
The examples below present Java methods that could be used to perform this escaping:
------------------------------------------------------------------------------------------------------------------------
public String escapeDN (String name) {
//From RFC 2253 and the / character for JNDI
final char[] META_CHARS = {´+´, ´"´, ´<´, ´>´, ´;´, ´/´};
String escapedStr = new String(name);
//Backslash is both a Java and an LDAP escape character, so escape it first
escapedStr = escapedStr.replaceAll("\\\\\\\\","\\\\\\\\");
//Positional characters - see RFC 2253
escapedStr = escapedStr.replaceAll("\^#","\\\\\\\\#");
escapedStr = escapedStr.replaceAll("\^ | $","\\\\\\\\ ");
for (int i=0;i < META_CHARS.length;i++) {
escapedStr = escapedStr.replaceAll("\\\\"+META_CHARS[i],"\\\\\\\\" + META_CHARS[i]);
}
return escapedStr;
}
Note, that the backslash character is a Java String literal and a regular expression escape character.
public String escapeSearchFilter (String filter) {
//From RFC 2254
String escapedStr = new String(filter);
escapedStr = escapedStr.replaceAll("\\\\\\\\","\\\\\\\\5c");
escapedStr = escapedStr.replaceAll("\\\\\*","\\\\\\\\2a");
escapedStr = escapedStr.replaceAll("\\\\(","\\\\\\\\28");
escapedStr = escapedStr.replaceAll("\\\\)","\\\\\\\\29");
escapedStr = escapedStr.replaceAll("\\\\"+Character.toString(´\\u0000´), "\\\\\\\\00");
return escapedStr;
}
Optimized version of the previous code (about 8 times faster for escaping LDAP search and 13 times for escapeDN on my PC). Further ,
public static String escapeDN(String name) {
StringBuffer sb = new StringBuffer(); // If using JDK >= 1.5 consider using StringBuilder
if ((name.length() > 0) && ((name.charAt(0) == ´ ´) || (name.charAt(0) == ´#´))) {
sb.append(´\\\\´); // add the leading backslash if needed
}
for (int i = 0; i < name.length(); i++) {
char curChar = name.charAt(i);
switch (curChar) {
case ´\\\\´:
sb.append("\\\\\\\\");
break;
case ´,´:
sb.append("\\\\,");
break;
case ´+´:
sb.append("\\\\+");
break;
case ´"´:
sb.append("\\\\\\"");
break;
case ´<´:
sb.append("\\\\<");
break;
case ´>´:
sb.append("\\\\>");
break;
case ´;´:
sb.append("\\\\;");
break;
default:
sb.append(curChar);
}
}
if ((name.length() > 1) && (name.charAt(name.length() - 1) == ´ ´)) {
sb.insert(sb.length() - 1, ´\\\\´); // add the trailing backslash if needed
}
return sb.toString();
}
public static final String escapeLDAPSearchFilter(String filter) {
StringBuffer sb = new StringBuffer(); // If using JDK >= 1.5 consider using StringBuilder
for (int i = 0; i < filter.length(); i++) {
char curChar = filter.charAt(i);
switch (curChar) {
case ´\\\\´:
sb.append("\\\\5c");
break;
case ´\*´:
sb.append("\\\\2a");
break;
case ´(´:
sb.append("\\\\28");
break;
case ´)´:
sb.append("\\\\29");
break;
case ´\\u0000´:
sb.append("\\\\00");
break;
default:
sb.append(curChar);
}
}
return sb.toString();
}
Test class for assertion :
-----------------------------------------------------------
//escapeDN
assertEquals("No special characters to escape", "Hello?", escapeDN("Hello?"));
assertEquals("leading #", "\\\\# Hello?", escapeDN("# Hello?"));
assertEquals("leading space", "\\\\ Hello?", escapeDN(" Hello?"));
assertEquals("trailing space", "Hello?\\\\ ", escapeDN("Hello? "));
assertEquals("only 3 spaces", "\\\\ \\\\ ", escapeDN(" "));
assertEquals("Christmas Tree DN", "\\\\ Hello\\\\\\\\ \\\\+ \\\\, \\\\\\"World\\\\\\" \\\\;\\\\ ", Test.escapeDN(" Hello\\\\ + , \\"World\\" ; "));
Special case :
assertEquals("No special characters to escape", "Hi This is a test #??", SecTool.escapeLDAPSearchFilter("Hi This is a test #??"));
assertEquals("LDAP Christams Tree", "Hi \\\\28This\\\\29 = is \\\\2a a \\\\5c test # ? ? ?", SecTool.escapeLDAPSearchFilter("Hi (This) = is \* a \\\\ test # ? ? ?"));
How do you fix the LDAP Injection vulnerability?
=======================================================
Input validation!!! The underlying code needs to verify the correct input using a white list. If the input is verified against a white list using a regular expression then the input could be rejected and the end user would need to input the correct data. Don´t let a malicious user mis-use your application. Verify that the input is validated and that there is not the ability to inject additional ldap information, especially the () | \* characters.
4