Difference between revisions of "Preventing LDAP Injection in Java"

From OWASP
Jump to: navigation, search
(Added optimized version)
(Reverting to last version not containing links to s1.shard.jp)
 
(17 intermediate revisions by 6 users not shown)
Line 1: Line 1:
This article is being [http://www.owasp.org/index.php/Talk:Preventing_LDAP_Injection_in_Java discussed].
+
==Status==
 +
Needs to be reviewed
  
The best way to prevent LDAP injection is to use a positive validation scheme for ensuring that the data going into your queries doesn't contain any attacks. You can read more in [[:Category:OWASP Guide Project|the OWASP Guide]] about input validation.
+
==Approach==
 +
The best way to prevent LDAP injection is to use a positive validation scheme for ensuring that the data going into your queries doesn't contain any attacks. You can read more in [[:Category:OWASP Guide Project|the OWASP Development Guide]] about input validation.
  
 
However, in some cases, it is necessary to include special characters in input that is passed into an LDAP query.  In this case, using escaping can prevent the LDAP interpreter from thinking those special characters are actually LDAP query.  Rather, the encoding lets the interpreter treat those special characters as data.
 
However, in some cases, it is necessary to include special characters in input that is passed into an LDAP query.  In this case, using escaping can prevent the LDAP interpreter from thinking those special characters are actually LDAP query.  Rather, the encoding lets the interpreter treat those special characters as data.
Line 7: Line 9:
 
Here are a few methods for escaping certain meta-characters in LDAP queries. Both the distinguished name (DN) and the search filter have their own sets of meta-characters.  In the case of Java, it is also necessary to escape any JNDI meta-characters, since java uses JNDI to perform LDAP queries.
 
Here are a few methods for escaping certain meta-characters in LDAP queries. Both the distinguished name (DN) and the search filter have their own sets of meta-characters.  In the case of Java, it is also necessary to escape any JNDI meta-characters, since java uses JNDI to perform LDAP queries.
  
 +
    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();
 +
    }
  
The examples below present Java methods that could be used to perform this escaping:
+
Escaping the search filter:
  
<em>Note: This is untested code</em> --[[User:Stephendv|Stephendv]] 05:08, 10 July 2006 (EDT)
+
    public static final String escapeLDAPSearchFilter(String filter) {
  public String escapeDN (String name) {
+
         StringBuffer sb = new StringBuffer(); // If using JDK >= 1.5 consider using StringBuilder
         //From RFC 2253 and the / character for JNDI
+
         for (int i = 0; i < filter.length(); i++) {
         final char[] META_CHARS = {'+', '"', '<', '>', ';', '/'};
+
            char curChar = filter.charAt(i);
        String escapedStr = new String(name);
+
            switch (curChar) {
        //Backslash is both a Java and an LDAP escape character, so escape it first
+
                case '\\':
        escapedStr = escapedStr.replaceAll("\\\\","\\\\");
+
                    sb.append("\\5c");
        //Positional characters - see RFC 2253
+
                    break;
        escapedStr = escapedStr.replaceAll("^#","\\\\#");
+
                case '*':
        escapedStr = escapedStr.replaceAll("^ | $","\\\\ ");
+
                    sb.append("\\2a");
        for (int i=0;i < META_CHARS.length;i++) {
+
                    break;
            escapedStr = escapedStr.replaceAll("\\"+META_CHARS[i],"\\\\" + META_CHARS[i]);
+
                case '(':
 +
                    sb.append("\\28");
 +
                    break;
 +
                case ')':
 +
                    sb.append("\\29");
 +
                    break;
 +
                case '\u0000':
 +
                    sb.append("\\00");  
 +
                    break;
 +
                default:
 +
                    sb.append(curChar);
 +
            }
 
         }
 
         }
         return escapedStr;
+
         return sb.toString();
 
     }
 
     }
  
Note, that the backslash character is a Java String literal and a regular expression escape character.
+
Test class:
   
+
  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 using precompiled patterns (speed increase is 20% on my PC).--[[User:Thierry.deleeuw|Thierry.deleeuw]] 16:11, 29 April 2007 (EDT)
+
        //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\" ; "));
 +
 
 +
        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 # ç à ô"));
  
    private static final Pattern BACKSLASH_PATTERN = Pattern.compile("\\\\");
 
    private static final Pattern STAR_PATTERN = Pattern.compile("\\*");
 
    private static final Pattern OPEN_PARENTHESIS_PATTERN = Pattern.compile("\\(");
 
    private static final Pattern CLOSE_PARENTHESIS_PATTERN = Pattern.compile("\\)");
 
    private static final Pattern NULL_CHARACTER_PATTERN = Pattern.compile("\\" + Character.toString('\u0000'));
 
    public static final String escapeLDAPSearchFilter(String filter) {
 
        //From RFC 2254
 
        Matcher match = BACKSLASH_PATTERN.matcher(filter);
 
        String result = match.replaceAll("\\\\5c");
 
        match = STAR_PATTERN.matcher(result);
 
        result = match.replaceAll("\\\\2a");
 
        match = OPEN_PARENTHESIS_PATTERN.matcher(result);
 
        result = match.replaceAll("\\\\28");
 
        match = CLOSE_PARENTHESIS_PATTERN.matcher(result);
 
        result = match.replaceAll("\\\\29");
 
        match = NULL_CHARACTER_PATTERN.matcher(result);
 
        result = match.replaceAll("\\\\00");
 
        return result;
 
    }
 
 
[[Category:OWASP Java Project]]
 
[[Category:OWASP Java Project]]

Latest revision as of 07:50, 3 June 2009

Status

Needs to be reviewed

Approach

The best way to prevent LDAP injection is to use a positive validation scheme for ensuring that the data going into your queries doesn't contain any attacks. You can read more in the OWASP Development Guide about input validation.

However, in some cases, it is necessary to include special characters in input that is passed into an LDAP query. In this case, using escaping can prevent the LDAP interpreter from thinking those special characters are actually LDAP query. Rather, the encoding lets the interpreter treat those special characters as data.

Here are a few methods for escaping certain meta-characters in LDAP queries. Both the distinguished name (DN) and the search filter have their own sets of meta-characters. In the case of Java, it is also necessary to escape any JNDI meta-characters, since java uses JNDI to perform LDAP queries.

   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();
   }

Escaping the search filter:

   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:

       //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\" ; "));
       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 # ç à ô"));