The blog of a gypsy engineer

Software security, electronics, DIY and traveling.

LDAP injections

Everybody knows about SQL injections. It’s like a celebrity in the world of software security. But there are much more many different types of injection attacks which may feel jealous about popularity of SQL injections. That’s not fair. Let’s try to feel the gap, and talk about LDAP injections.

What is LDAP?

LDAP stands for Lightweight Directory Access Protocol. It’s a client-server binary protocol which lets clients access directory services. LDAP normally runs over TCP/IP, but it’s also much better to use TLS. LDAP is defined in RFC 2251.

To make a long story short, LDAP server contains an attribute-based database. LDAP defines a way how clients can update and search data in this database.

There are several implementations of LDAP client and servers, for example:

  • OpenLDAP
  • Java has an LDAP client API (JNDI)

What is an LDAP injection?

LDAP injection is an injection attack in which an attacker can insert malicious LDAP statements in to the original LDAP query used by an application. As a result, an attacker may be able to send malicious LDAP requests to the LDAP server which may lead to security implications such as reading or updating sensitive information. LDAP injections usually occur because an application fails to properly sanitize untrusted data which may come from an adversary.

An example of LDAP injection

Let’s assume that we have an internal LDAP server behind the firewall. This LDAP server is used by an application for user authentication. The application requests user’s credentials, and then checks if they are valid by searching for a record in the LDAP database.

For demo purposes, we can use a local LDAP server based on Ldaptor (it requires Twisted which can be installed with pip). Ldaptor team kindly provides an example of LDAP server which I borrowed:

The server above can be simply started with `python ldapserver.py` command.

Here is an example of vulnerable application:

The application takes a username and a password from command line parameters, and then sends a search request to the LDAP server to get a corresponding user account. If a record was found, it grants access. Here is how it runs with valid and invalid credentials:

$ javac -d classes LDAPLogin.java 
$ java -classpath classes LDAPLogin bob secret
LDAP query: (&(uid=bob)(userPassword=secret))
Access granted
$ java -classpath classes LDAPLogin bob wrong
LDAP query: (&(uid=bob)(userPassword=wrong))
Access denied

The problem here is that the application puts a username and a password “as is” to an LDAP search query. This allows an attacker to modify the query which the application sends to the LDAP server. It can be used, for example, for bypassing authentication. If an attacker doesn’t know Bob’s password, he can provide “bob)(|(uid=bob” string as a username and “wrong)” as a password which let him in.

$ java -classpath classes LDAPLogin "bob)(|(uid=bob" "wrong)"
LDAP query: (&(uid=bob)(|(uid=bob)(userPassword=wrong)))
Access granted

You can see that the application sent “(&(uid=bob)(|(uid=bob)(userPassword=wrong)))” request to the LDAP server. LDAP uses “Polish notation” for search queries. This request means “(uid == bob) and (uid=bob or userPassword=wrong)”. This statement is always true for the record for Bob’s account, so that the search request always returns a record for Bob’s account even if provided password is wrong. As a result, the attacker can bypass authentication.

The actual exploit for an LDAP injection may depend on multiple things such as:

  • Original LDAP search request
  • Version of LDAP server
  • Application logic

There are three main types of LDAP search queries:

  • A query doesn’t use any logical operators. It looks like “(field=value)”. Even if we can put anything to `value`, we can’t use any logical operator in such search request because Polish notation requires logical operators to go first. The only option here is to try to exploit a vulnerability of LDAP server (for instance, a buffer overflow, if it has it) which can be triggered by a malicious search request.
  • AND operator goes first. It looks like “(&(field1=value1)(field2=value2))”. This kind of search request is used by the application above.
  • OR operator goes first. It looks like “(|(field1=value1)(field2=value2))”.

It’s good to know what type of search query is used by a vulnerable application for successful attack.

An application may also use LDAP request to update data in database. LDAP uses LDAP Data Interchange Format (LDIF) for updating database. LDIF represents a list of commands for adding, deleting and modifying database content. Basically it’s a set of records, one record for each command. `ldapserver.py` above contains an example of LDIF data which adds three records to the directory. If an attacker can inject malicious data to LDIF, he can use a couple of CRLF sequences, and then add malicious commands. It’s kinda similar to HTTP request splitting.

Version of LDAP software of server side may also matter. Sometimes an attacker can use an LDAP injection in a way that the query to LDAP server results to a couple of LDAP search request. If an LDAP server uses only first search request, and ignore others, then it may help for successful attack. Note that syntax of LDAP search requests doesn’t allow commenting out the rest of request like it’s possible in SQL.

Blind LDAP injections

Blind LDAP injections are similar to blind SQL injections. An application may be vulnerable to LDAP injection, but it may not print out all requested fields. This doesn’t allow an attacker to simply dump the content of LDAP directory. But if the application shows somehow if LDAP search requests with injected data succeed or not, then this behavior allows an attacker to ask yes/not questions. As a result, it may be possible to implement an efficient bruteforce attack, and extract data from the LDAP database.

Let’s consider the following application. It takes a user ID, search for a corresponding record in LDAP directory. If a record was found, it prints out user’s phone number. If not, it says that nobody found. The application can use LDAP server above.

Here is how it’s supposed to be normally used:

$ javac -d classes LDAPInfo.java 
$ java -cp classes LDAPInfo bob
LDAP query: (&(uid=bob)(objectClass=person))
Phone: telephoneNumber: 555-9999
$ java -cp classes LDAPInfo boba
LDAP query: (&(uid=boba)(objectClass=person))
Nobody found!

You might notice that LDAPInfo is vulnerable to LDAP-injection attack. Although it prints out only user’s phone number, an attacker can still extract data from other fields. For example, an attacker can check if `userPassword` field for Bob’s account starts with ‘a’ letter:

java -cp classes LDAPInfo "bob)(userPassword=a*"
LDAP query: (&(uid=bob)(userPassword=a*)(objectClass=person))
Nobody found!

You can see that if you pass “bob)(userPassword=a*” string as a user name, it results to ‘Nobody found!’ message. The application built “(&(uid=bob)(userPassword=a*)(objectClass=person))” search query which didn’t return anything because `userPassword` for Bob’s record doesn’t start with ‘a’. Now an attacker can enumerate first letters:

java -cp classes LDAPInfo "bob)(userPassword=a*"
LDAP query: (&(uid=bob)(userPassword=a*)(objectClass=person))
Nobody found!
java -cp classes LDAPInfo "bob)(userPassword=b*"
LDAP query: (&(uid=bob)(userPassword=b*)(objectClass=person))
Nobody found!
java -cp classes LDAPInfo "bob)(userPassword=c*"
LDAP query: (&(uid=bob)(userPassword=c*)(objectClass=person))
Nobody found!
[...]
java -cp classes LDAPInfo "bob)(userPassword=s*"
LDAP query: (&(uid=bob)(userPassword=s*)(objectClass=person))
Phone: telephoneNumber: 555-9999

Once we reached letter ‘c’, the application returned Bob’s phone number. That means that Bob’s password starts with ‘s’. Then, an attacker starts searching for second letter with usernames like “bob)(userPassword=sa*”, “bob)(userPassword=sb*”, “bob)(userPassword=sc*” and so on. This allows to implement an efficient bruteforce attack, and extract the password from the database.

Here is a simple script which demonstrates such a bruteforce attack:

How to prevent LDAP injections

Nothing surprising here. Input validation and sanitation help to prevent LDAP injections. Applications should escape all data that comes from untrusted sources and which is used in LDAP queries. OWASP has an article about it (see below) which may be applied not only to Web applications.

Another measure is disabling indexing of fields which may contain sensitive information like passwords. For example, If userPassoword fieds is not indexed, then a search request with (userPassword=a*) statement will result to a error like the following:

$ ldapsearch -h ldap.server m -x -b "dc=test,dc=com" "(&(uid=test)(userPassword=a*))"
....
# search result
search: 2
result: 53 Server is unwilling to perform
text: Function Not Implemented, search filter attribute userpassword is not indexed/cataloged

# numResponses: 1

Although, an attacker may still be able to extract info from other indexed fields.

Links:

MicroPython on ESP8266: sending data to ThingSpeak

When you play with new microcontroller, first thing you usually do is driving an LED. That’s a classic “Hello World!” project for microcontrollers. That’s what I did when I was playing first time with ESP8266 and MicroPython. Let’s move on, and implement another classic project – measuring temperature and humidity with DHT22 sensor. But we don’t want to be quiet, so we are going to share this so important data on the Internet. ThingSpeak will help us with it. Let’s add a new warrior to the army of Internet of Shit!

Sending temperature and humidity to ThingSpeak with ESP8266 and MicroPython

Read More

Problems with running MicroPython on ESP8266 with 512K

In my previous post about running MicroPython on ESP8266, I mentioned that ESP8266 boards may have different amount of flash. Similarly there are two versions of MicroPython: limited version for 512K, and full version for boards which have more than 512K of flash. In that post, I played with ESP-07 which had only 512K, so I had to use a limited version of MicroPython. This limited MicroPython version was enough just to turn on/off an LED, but it turned out that it actually doesn’t work well.

ESP8266

Read More

Getting started with ESP8266 and MicroPython

I like the idea of Internet of Things (IoT) which is becoming so popular. We have everything connected to the Internet: TVs, printers, fridges, cars, even teeth brushes, etc. We already have botnets which consist of IoT devices, and are used for massive DDoS attacks. I personally prefer calling it “Internet of Shit” because sometimes it’s not clear why some devices connect to the Internet. By the way, there is a twitter called “Internet of Shit”. I highly recommend to follow.

Using those fancy IoT devices is fun. Furthermore, sometimes such devices are even helpful. But it’s more fun to participate more actively. For example, you can create your own IoT device with blackjack and hookers. God bless those people who developed ESP8266 boards which now allow everybody to build their own IoT devices. As you may know, ESP8266 boards are extremely cheap. And I would say they are relatively easy to use (especially if you know about Google).

I was going to try ESP8266 controllers for long time. Finally, I did it, and want to share my experience in hope it may be useful. I found a lot of articles about ESP8266 and NodeMCU firmware which allows you to run Lua scripts on your ESP8266 board. That’s cool, but the problem is that I don’t know anything about Lua language. Another problem is that I am lazy in this time of year, so I didn’t want to learn Lua. But luckily I know Python a little bit, and there is MicroPython project which allows you to run Python scripts on embedded devices including ESP8266.

Here is a tutorial how to get started with ESP8266 and MicroPython.

Русская версия – Как запустить MicroPython на ESP8266

ESP8266 ESP-07 and other little things

Read More

Python marshal module fuzzing

The marshal module provides a serialization mechanism for Python values. In other words, the module contains functions for writing/reading Python objects in a binary format. Unfortunately the format is undocumented, and Python maintainers may change the format in backward incompatible ways between Python version. The marshal module is used internally by other Python components, for example, for reading and writing .pyc files which contain pseudo-compiled Python code. But Python also has public API to access this serialization mechanism.

This post shows how the marshal module can be quickly tested with a simple dumb fuzzer, and why the module shouldn’t be used with untrusted data.

Read More

Spelling error report

The following text will be sent to our editors: