2007-03-08 16:27:45
LDAP Injection
After taking some time to learn about SQL injection, I decided to learn a little about other types of injection; specifically LDAP injection. So after reading some whitepapers, I started my search for an LDAP enabled site in which to test my new knowledge (or lack thereof). I found a website which used LDAP to store user information (already knowing what LDAP was used for, I narrowed my initial search to phone and user listings). Now it was off to the races.I first started by doing my usual SQL injecton test of entering a single quote into a field and submitting the form. As expected, a normal page was returned and I was informed that my search yielded no results. Ok. Let's change it up a little. In an LDAP query an asterisk is a wildcard, so I'll enter that into the search field and submit again. Same page, no results. So it appears that the page was either escaping my asterisk or converting it to its hex value. Now I started using my new injection knowledge.
Sections of LDAP queries are separated by (or contained in) parentheses. So I entered a right parenthese and submitted the form. Bingo! Server error page. Normally getting this far doesn't mean anything, because security concious programmers will slim down the output so as to not give away any code or other information about the inner-workings of the page. This one, however, gave me exactly what I needed.
The server error page gave several possible reasons that my request failed, but the last one struck me as awesome (I've ZZZ'd out info which might lead you to the vulnerable site):
Your converted Search filter is as follows: (& (sn=)*) (!(|(sn=* *ZZZ*)(msExchHideFromAddressLists=TRUE)(displayName=*(ZZZ)*)(displayName=ZZZ*)(cn=ZZZ*)))).
That little tidbit (along with seeing from the URL that the page was written in ColdFusion (CF)) gives me two things. First, it gives my LDAP query "code" and second, I know that I am only working with a search filter. CF comes with it's own tags for LDAP queries and "filter" is one of the parameters. So what does that tell us? Well, it tells us that no matter what we inject, we can't change the type of this query to a Modify or anything else because the CF tag (which is out of the bounds of our injection) specified a simple query. So let's break this down.
(& (sn=
That is the first part of the query as supplied by the CF page.
)
That is what we entered into the input box.
*) (!(|(sn=* *ZZZ*)(msExchHideFromAddressLists=TRUE)(displayName=*(ZZZ)*)(displayName=ZZZ*)(cn=ZZZ*))))
That is the third part of the query as supplied by the CF page.What does all of that mean in plain English though? Let's work from right to left, shall we?
(|(sn=* *ZZZ*)(msExchHideFromAddressLists=TRUE)(displayName=*(ZZZ)*)(displayName=ZZZ*)(cn=ZZZ*))
The first character of this section of the query is a pipe, which is the OR operator. If this query were to be run by itself, it would return results where:
- Surname contains text, a space, then ZZZ or
- msExchHideFromAddressLists attribute is TRUE or
- Display Name contains (ZZZ) or
- Display Name begins with ZZZ or
- Common Name begins with ZZZ
(!(|(sn=* *ZZZ*)(msExchHideFromAddressLists=TRUE)(displayName=*(ZZZ)*)(displayName=ZZZ*)(cn=ZZZ*)))
Now, the first character of the query is an exclamation point (bang), which is the NOT operator. If this query were to be run it would return results where any of the five aforementioned statements were FALSE. In other terms, if any of those five statements were TRUE, don't show that result. Now to add the beginning.
(& (sn=USERINPUT*) (!(|(sn=* *ZZZ*)(msExchHideFromAddressLists=TRUE)(displayName=*(ZZZ)*)(displayName=ZZZ*)(cn=ZZZ*))))
The first character of the query is now an ampersand, which is the AND operator. Now the query would return results where the Surname begins with what we entered in the form as long as the five aforementioned statements weren't true. Now that we know what the query is doing, how can we use injection to change it?We already know that parentheses do not get "stripped" by any means, so that is our line of attack. Looking only at the first portion of the query, we know that what we enter gets prepended with
(& (sn=
and postpended with
*)
What we enter in between must keep the query valid. Entering a single right parenthese as we did before was one parenthese too many, but if we're careful and enter the corrent number of right and left parentheses, we can still keep the query valid. We know we can't get a listing of everything in the directory by searching for an asterisk (wildcard) using the search form, but we really want that list of everything. Let's enter this into the search field:
*)(sn=
That would make the first part of the query look like this:
(& (sn=*)(sn=*)
which when postpended with the rest of the query, is valid. You might say, why are you searching for the same thing twice, isn't that redundant? Yes. But it works. For some reason the programmer of this page filtered out single asterisks, but they didn't filter them out if there was more text with it. We used that to our advantage and constructed this query:
(& (sn=*)(sn=*) (!(|(sn=* *ZZZ*)(msExchHideFromAddressLists=TRUE)(displayName=*(ZZZ)*)(displayName=ZZZ*)(cn=ZZZ*))))
which will give us every result in the directory where those five statements are false.Of course, you could take this further and do more clever searches, but this should get you on the right path to understanding LDAP injection and how to help prevent it in your own code.
Back