What is LDAP injection ? 

       LDAP injection works in much the same manner as SQL injection, a type of security exploit in which the attacker adds SQL (Structured Query Language) code to a Web form input box to gain access to resources or make changes to data. According to security experts, the main reason that LDAP injection and similar exploits are on the rise is the fact that security is not sufficiently emphasized in application development. To protect the integrity of Web sites and applications, experts recommend the implementation of simple precautions during development, such as controlling the types and numbers of characters that are accepted by input boxes.

    LDAP injection is an application-specific vulnerability that commonly occurs due to missing or weak input validation functions prior to processing and allowing persistence of data in LDAP. This weakness would allow a hacker to use malicious LDAP attributes or Java Naming and Directory Interface (JNDI) API query/store functions to inject or manipulate or steal personal information from an LDAP repository.

    LDAP injection is also possible due to exploits of an insecure LDAP lookup configuration (using "Directory Manager") and missing LDAP access control policies.

    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.

Comments:

rfc 2254 actually adresses how to fix these ldap injection bugs in section 4 on page 4:
"
If a value should contain any of the following characters

Character ASCII value
---------------------------
* 0x2a
( 0x28
) 0x29
\ 0x5c
NUL 0x00

the character must be encoded as the backslash '\' character (ASCII
0x5c) followed by the two hexadecimal digits representing the ASCII
value of the encoded character. The case of the two hexadecimal
digits is not significant.

This simple escaping mechanism eliminates filter-parsing ambiguities
and allows any filter that can be represented in LDAP to be
represented as a NUL-terminated string. Other characters besides the
ones listed above may be escaped using this mechanism, for example,
non-printing characters."

Posted by Ilja van Sprundel on July 13, 2008 at 07:02 AM IST #

Thank you Ilja van Sprundel for quoting the RFC http://www.ietf.org/rfc/rfc2254.txt

Posted by shankar on July 13, 2008 at 01:25 PM IST #

Post a Comment:
  • HTML Syntax: NOT allowed

This blog copyright 2009 by shankar