LDAP Injection


Project: WASC Threat Classification

Threat Type: Attack

Reference ID: WASC-29

 

LDAP Injection

LDAP Injection is an attack technique used to exploit web sites that construct LDAP statements from user-supplied input.

Lightweight Directory Access Protocol (LDAP) is an open-standard protocol for both querying and manipulating X.500 directory services. The LDAP protocol runs over Internet transport protocols, such as TCP. Web applications may use user-supplied input to create custom LDAP statements for dynamic web page requests.

When a web application fails to properly sanitize user-supplied input, it is possible for an attacker to alter the construction of an LDAP statement. When an attacker is able to modify an LDAP statement, the process will run with the same permissions as the component that executed the command. (e.g. Database server, Web application server, Web server, etc.). This can cause serious security problems where the permissions grant the rights to query, modify or remove anything inside the LDAP tree. The same advanced exploitation techniques available in SQL Injection can also be similarly applied in LDAP Injection.

 

Example

Vulnerable code:

 line   1 using System;
 line   2 using System.Configuration;
 line   3 using System.Data;
 line   4 using System.Web;
 line   5 using System.Web.Security;
 line   6 using System.Web.UI;
 line   7 using System.Web.UI.HtmlControls;
 line   8 using System.Web.UI.WebControls;
 line   9 using System.Web.UI.WebControls.WebParts;
 line  10 
 line  11 using System.DirectoryServices;
 line  12 
 line  13 public partial class _Default : System.Web.UI.Page 
 line  14 {
 line  15     protected void Page_Load(object sender, EventArgs e)
 line  16     {
 line  17         string userName;
 line  18         DirectoryEntry entry;
 line  19 
 line  20         userName = Request.QueryString["user"];
 line  21 
 line  22         if (string.IsNullOrEmpty(userName))
 line  23         {
 line  24             Response.Write("<b>Invalid request. Please specify valid user name</b></br>");
 line  25             Response.End();
 line  26 
 line  27             return;
 line  28         }
 line  29 
 line  30         DirectorySearcher searcher = new DirectorySearcher();
 line  31 
 line  32         searcher.Filter = "(&(samAccountName=" + userName + "))";
 line  33 
 line  34         SearchResultCollection results = searcher.FindAll();
 line  35 
 line  36         foreach (SearchResult result in results)
 line  37         {
 line  38             entry = result.GetDirectoryEntry();
 line  39 
 line  40             Response.Write("<p>");
 line  41             Response.Write("<b><u>User information for : " + entry.Name + "</u></b><br>");
 line  42 
 line  43             foreach (string proName in entry.Properties.PropertyNames)
 line  44             {
 line  45                 Response.Write("<br>Property : " + proName);
 line  46 
 line  47                 foreach( object val in entry.Properties[proName] )
 line  48                 {
 line  49                     Response.Write("<br>Value: " + val.ToString());
 line  50                 }
 line  51             }
 line  52 
 line  53             Response.Write("</p>");
 line  54         }
 line  55     }
 line  56 }
 

Looking at the code, we see on line 20 that the userName variable is initialized with the parameter user and then quickly validated to see if the value is empty or null. If the value is not empty, the userName is used to initialize the filter property on line 32. In this scenario, the attacker has complete control over what will be queried on the LDAP server, and he will get the result of the query when the code hits line 34 to 53 where all the results and their attributes are displayed back to the user.

 

Attack Example

http://example/default.aspx?user=*

In the example above, we send the * character in the user parameter which will result in the filter variable in the code to be initialized with(samAccountName=*). The resulting LDAP statement will make the server return any object that contains the samAccountName attribute. In addition, the attacker can specify other attributes to search for and the page will return an object matching the query.

 

Mitigation

The escape sequence for properly using user supplied input into LDAP differs depending on if the user input is used to create the DN (Distinguished Name) or used as part of the search filter. The listings below shows the character that needs to be escape and the appropriate escape method for each case.

Used in DN - Requires \ escape

Used in Filter- Requires {\ASCII} escape

The code below implements the escape logic for both DN and Filter case. Use CanonicalizeStringForLdapFilter() to escape when the input is used to create the filter and CanonicalizeStringForLdapDN() for DN. In addition, both IsUserGivenStringPluggableIntoLdapSearchFilter and IsUserGivenStringPluggableIntoLdapDN can be used to detect the presence of restricted characters.

 

line   1 using System;
line   2 using System.Collections.Generic;
line   3 using System.Text;
line   4 
line   5 namespace LdapValidation
line   6 {
line   7     public class LdapCanonicaliztion
line   8     {
line   9         /// <summary>
line  10         /// Characters that must be escaped in an LDAP filter path
line  11         /// WARNING: Always keep '\\' at the very beginning to avoid recursive replacements
line  12         /// </summary>
line  13         private static char[] ldapFilterEscapeSequence = new char[] { '\\', '*', '(', ')', '\0', '/' };
line  14 
line  15         /// <summary>
line  16         /// Mapping strings of the LDAP filter escape sequence characters
line  17         /// </summary>
line  18         private static string[] ldapFilterEscapeSequenceCharacter = new string[] { "\\5c", "\\2a", "\\28", "\\29", "\\00", "\\2f" };
line  19 
line  20         /// <summary>
line  21         /// Characters that must be escaped in an LDAP DN path
line  22         /// </summary>
line  23         private static char[] ldapDnEscapeSequence = new char[] { '\\', ',', '+', '"', '<', '>',';' };
line  24 
line  25         /// <summary>
line  26         /// Canonicalize a ldap filter string by inserting LDAP escape sequences.
line  27         /// </summary>
line  28         /// <param name="userInput">User input string to canonicalize</param>
line  29         /// <returns>Canonicalized user input so it can be used in LDAP filter</returns>
line  30         public static string CanonicalizeStringForLdapFilter(string userInput)
line  31         {
line  32             if (String.IsNullOrEmpty(userInput))
line  33             {
line  34                 return userInput;
line  35             }
line  36 
line  37             string name = (string)userInput.Clone();
line  38 
line  39             for (int charIndex = 0; charIndex < ldapFilterEscapeSequence.Length; ++charIndex)
line  40             {
line  41                 int index = name.IndexOf(ldapFilterEscapeSequence[charIndex]);
line  42                 if (index != -1)
line  43                 {
line  44                     name = name.Replace(new String(ldapFilterEscapeSequence[charIndex], 1), ldapFilterEscapeSequenceCharacter[charIndex]);
line  45                 }
line  46             }
line  47 
line  48             return name;
line  49         }
line  50 
line  51         /// <summary>
line  52         /// Canonicalize a ldap dn string by inserting LDAP escape sequences.
line  53         /// </summary>
line  54         /// <param name="userInput">User input string to canonicalize</param>
line  55         /// <returns>Canonicalized user input so it can be used in LDAP filter</returns>
line  56         public static string CanonicalizeStringForLdapDN(string userInput)
line  57         {
line  58             if (String.IsNullOrEmpty(userInput))
line  59             {
line  60                 return userInput;
line  61             }
line  62 
line  63             string name = (string)userInput.Clone();
line  64 
line  65             for (int charIndex = 0; charIndex < ldapDnEscapeSequence.Length; ++charIndex)
line  66             {
line  67                 int index = name.IndexOf(ldapDnEscapeSequence[charIndex]);
line  68                 if (index != -1)
line  69                 {
line  70                     name = name.Replace(new string(ldapDnEscapeSequence[charIndex], 1), @"\" + ldapDnEscapeSequence[charIndex] );
line  71                 }
line  72             }
line  73 
line  74             return name;
line  75         }
line  76 
line  77         /// <summary>
line  78         /// Ensure that a user provided string can be plugged into an LDAP search filter 
line  79         /// such that there is no risk of an LDAP injection attack.
line  80         /// </summary>
line  81         /// <param name="userInput">String value to check.</param>
line  82         /// <returns>True if value is valid or null, false otherwise.</returns>
line  83         public static bool IsUserGivenStringPluggableIntoLdapSearchFilter(string userInput)
line  84         {
line  85             if (string.IsNullOrEmpty(userInput))
line  86             {
line  87                 return true;
line  88             }
line  89 
line  90             if (userInput.IndexOfAny(ldapDnEscapeSequence) != -1)
line  91             {
line  92                 return false;
line  93             }
line  94 
line  95             return true;
line  96         }
line  97 
line  98         /// <summary>
line  99         /// Ensure that a user provided string can be plugged into an LDAP DN 
line 100         /// such that there is no risk of an LDAP injection attack.
line 101         /// </summary>
line 102         /// <param name="userInput">String value to check.</param>
line 103         /// <returns>True if value is valid or null, false otherwise.</returns>
line 104         public static bool IsUserGivenStringPluggableIntoLdapDN(string userInput)
line 105         {
line 106             if (string.IsNullOrEmpty(userInput))
line 107             {
line 108                 return true;
line 109             }
line 110 
line 111             if (userInput.IndexOfAny(ldapFilterEscapeSequence) != -1)
line 112             {
line 113                 return false;
line 114             }
line 115 
line 116             return true;
line 117         }
line 118     }
line 119 }

References

"LDAP Injection: Are Your Web Applications Vulnerable?", By Sacha Faust - SPI Dynamics

[1] http://ebook.security-portal.cz/book/hacking_method/LDAP/LDAPinjection.pdf

 

"LDAP Injection & Blind LDAP Injection"

[2] http://www.blackhat.com/presentations/bh-europe-08/Alonso-Parada/Whitepaper/bh-eu-08-alonso-parada-WP.pdf

 

"A String Representation of LDAP Search Filters"

[3] http://www.ietf.org/rfc/rfc1960.txt

 

"Understanding LDAP"

[4] http://www.redbooks.ibm.com/redbooks/SG244986.html

 

"LDAP Resources"

[5] http://ldapman.org/

 

Failure to Sanitize Data into LDAP Queries ('LDAP Injection')

[6] http://cwe.mitre.org/data/definitions/90.html