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 (cn=%s)
:
- 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
input 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.
However:
- 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
stupid like
password=%s
)
- 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.