LDAP injections, reflexions
While searching for some security papers, I’ve been looking for something about LDAP injections.
Let’s have a look on what OWASP says on their site:
The same advanced exploitation techniques available in SQL Injection can be similarly applied in LDAP Injection.
There is a problem here: there are some big differences between SQL and LDAP, which lead to differences in security:
- in SQL, commands and values are expressed in the same input (a string containing a command). Several commands can be specified in one string:
"SELECT field FROM table WHERE ...; DROP TABLE xx;"
- while in LDAP, the filter contains only filter parameters, in a specific representation (RFC 2254). This representation uses a prefixed form (boolean operator comes before the fields), and each field must be enclosed between parenthesis.:
"( & (uid=john.doe) (objectClass=person) )
OWASP gives some examples, for a filter
- If a user puts “*” on box search, the system may return all the usernames on the LDAP base
- If a user puts “jonys) (| (password = * ) )”, it will generate the code bellow revealing jonys’ password
What ? One could make an application display the password ?!
The first line is true: any filter like
(uid=*) will return all users
having the uid attribute (this is called a presence filter). This won’t
allow to display specific fields, only select users. There is also a
default limit (500) for such requests on OpenLDAP, by default.
So, indeed, one has to check the number of expected results. To avoid problems, you should really give the list of fields you are requesting, and not request complete entries, when possible.
To my knowledge, the second affirmation is just plain wrong ! The resulting filter will be
( cn = jonys ) ( | (password = * ) )
This filter is invalid : the form
( request1 )( request2 ) is
invalid, in LDAP you must specify one filter, not two
ldapsearch -x -LL -h server -b 'dc=inl,dc=fr' '(uid=coin)(objectclass=person)' ldapsearch: ldap_search_ext: Bad search filter (-7)
There are some conditions to make an injection: there must be a filter
(before the user input) using a OR operator. For example, if using
( | (uid=%s) (condition2) ), the second part can be bypassed by using
name)(objectclass=*), since it will give:
( | (uid=name)(password=*))( (condition2) )
The second part of the filter is ignored, so the condition2 is ignored .. and the user name too.
- This is only true for OR (if the condition before was a AND, it seems the is not way to bypass the first condition.
- a OR is very unlikely
- The consequences are still limited: you can’t modify the data, only return a different set to the application.
- This won’t bypass a password check (in LDAP, passwords are checked using a BIND operation)
Being curious, I have continued my search and found .. nothing interesting. The same example seem to be put on many sites without understanding it, and without explanations.
The most useful link I’ve found is http://elladodelmal.blogspot.com/2007/10/ldap-injection-blind-ldap-injection_9021.html (spanish), which is more descriptive. However, the conclusion (part3) is a big surprise:
- they test the injections on several servers and ..
- no one has executed the second filter : AD has just ignored it, and so did OpenLDAP !
(BTW, I’m just wondering what is the interested a security article which gives no result .. just a random thought).
So, to summarize:
- LDAP is subject to enumeration attacks
- LDAP is more robust than SQL to injections, due to the separation between LDAP commands, field list, and filter. The prefixed form of the filter is also of some help.
- Under certain circumstances (query preceded by a OR), injection seems possible, even if consequences are not that interesting: a weak application trusting only the result set is needed.
- You have to rely on the fact that the server will silently ignore the second part of the request. It would be easy for a LDAP server to return an error on this case, and thus reduce the risks.
Even though, that does not prevent you from using some precautions:
- escape characters (remember: never trust user input) ! especially ( ) \ *
- never put a OR in the filter preceding the input part
- use the BIND operation to validate passwords (never do something
- when possible, validate the number of expected results vs actual results.
If anyone reading this has some more details, please leave a comment, I’d be happy to understand this correctly.