Word of advice: Escaping LDAP filter values in PHP

Second “word of advice” for PHP programmers is kind of related (and also referenced in) to the previous post in the series.

We are going to cover escaping of LDAP values in LDAP queries initiated from PHP.

Introduction to the problem

Honestly, often it seems that LDAP support in PHP alone is a second class citizen. Sure, things seem to be much better in its semi-official Zend Framework. But as it goes in PHP world, a lot of developers don’t use Zend Framework, and way too many don’t use any framework since the platform lacked proper official or semi-official one for more than a decade.

Even now in the days when the biggest PHP developer’s dilemma is which application framework to use for their projects, most of the 3rd party frameworks lack any level of advanced LDAP support. Developers are then still forced to cope with (for the PHP developers’ standards) low-level LDAP support if they need LDAP functionality in their apps.

That being said, I have to mention that you shouldn’t have to worry about anything on this issue if you’re using Zend Framework properly. Zend_Ldap_Filter_Abstract class engages escapeValue() method, which is used exactly for this – escaping of values used in LDAP queries.

Although basically a database, LDAP really is, and you realize it first time you have to connect to it, whole different story from your usual relational or noSQL databases you’re used to work with. That being said, even the most basic PHP LDAP API separates modification from read-only function to access it. It means that if you use LDAP only to authenticate your users and read their data (meaning: you don’t update anything intentionally through ldap_modify or some other such function) you shouldn’t face the worse result of “LDAP injection”. Worse case scenario in such environment would be attacker’s succesful logins which usually wouldn’t succeed. Bad enough, but no damage to your data.

That doesn’t mean you should approach issue of LDAP queries with unconsciousness. Hence this article. :-)

Usual attack vector for read-only unescaped LDAP queries

If your application uses unsecure filter such as this for retrieving user’s DN for the purpose of authentication against LDAP service:
(&(uid=)(objectClass=person))
in combination with unescaped value merged in place of <USERNAME> It will be fairly easy for an attacker to log into your application by providing value for username like
*)(userPassword=123456
Again, it’s an obvious flaw – if you let unescaped values into queries, providing such a clear opportunity to inject additional filter rules to the query by injection is a really big mistake (to understate the issue). But I have to say again – whole this “Word of advice” article series is based on real life experience with other people’s code.

Upper search injected with example above would result in query like this:
(&(uid=*)(userPassword=123456)(objectClass=person))
It’s a completely legitimate query which would return all of the users who have password of ‘123456‘. And as we all know, it’s the second most used password on the internet, so it probably is also on your corporate network. :-)

Combined with another potential security flaw, lack of the check if your query returned only the expected 1 result, attacker pretty much got authenticated as the first user listed in LDAP response.

The solution

RFC 2254 describes “String Representation of LDAP Search Filters“. Second part of the “4. String Search Filter Definition” describes what and how has to be escaped in LDAP queries to provide protection from unwanted value injections in LDAP filters. Quote:

Which basically means: in your LDAP class (or to any other relevant part of your code), you should introduce something like the following function:

What it does? It converts characters null (\\00), , () and * to \\003c2829 and 2a respectively.

It completely solves the issue of possible LDAP query injections. Just make sure you always escape input values for LDAP queries.

Author: Vedran Krivokuca

A developer living and working in Germany. Wannabe opensource contributor. Feeling strong of some social issues.

4 thoughts on “Word of advice: Escaping LDAP filter values in PHP”

  1. Thanks for this. However, I had to make a few modifications to get it to work for me. They may have primarily been because of code being escaped as it was displayed on your website, but not completely. Here’s what I ended up with:

    public static function escapeLdapFilter($str = ”) {

    // The characters that need to be escape.
    //
    // NOTE: It’s important that the slash is the first character replaced.
    // Otherwise the slash added by other replacements will then be
    // replaced as well, resulted in double-escaping all characters
    // replaced before the slashes were replaced.
    //
    $metaChars = array(
    chr(0x5c), // \
    chr(0x2a), // *
    chr(0x28), // (
    chr(0x29), // )
    chr(0x00) // NUL
    );

    // Build the list of the escaped versions of those characters.
    $quotedMetaChars = array ();
    foreach ($metaChars as $key => $value) {
    $quotedMetaChars[$key] = ‘\\’ .
    str_pad(dechex(ord($value)), 2, ‘0’, STR_PAD_LEFT);
    }

    // Make all the necessary replacements in the input string and return
    // the result.
    return str_replace($metaChars, $quotedMetaChars, $str);
    }

Leave a Reply