Enterprises frequently contain Active Directory environments to manage domain objects like users, organizations, departments, computers, and printers. Combine this with an increase in custom web applications and organizations naturally desire to integrate these two technologies together. This integration is an excellent way to create centralized authentication to their domain but also provides a method to query and manage their Active Directory environments.
It's import to remember that integrating these two technologies may also provide another attack surface against one's Active Directory environment by malicious actors. As such, it's critical to practice good web application security when integrating with Active Directory.
Before we move onto exploitation, let's first try to understand the protocol used to integrate web applications with Active Directory — LDAP (Lightweight Active Directory Protocol). Not only can LDAP query objects from a directory database, it can also be used for management and authentication.
Another thing to "keep in mind" is that LDAP is not the directory database itself. It is a service and protocol that provides a method to access directory databases. Additionally, LDAP is not exclusive to only Microsoft Active directory environments. There are several types of databases that employ LDAP like OpenLDAP.
SQL Injection is the typical attack method that comes to mind when people think of web application exploitation but LDAP integrated websites may also be exploited through injection. There are significant differences between SQL injection and LDAP injection as the syntax differs greatly between the two.
To illustrate LDAP injection, I have made a vulnerable web application with LDAP integration and will demonstrate a simple injection below. Before demonstrating LDAP injection, let's first cover:
- Understanding Directory Database Structures.
- Understanding LDAP Syntax.
Directory Database Structure
Directory databases can be very complex and extremely large depending on the organization. As such, I'll be using a LDIF (LDAP Interchange Format) file to illustrate a simple directory structure. LDIF files are simply plain text files which represent directory data and LDAP commands. They are also used to read, write, and update data in a directory. Below you can see a sample LDIF template file.
Lines 8-10: Here, we are defining the top-level domain "org".
Lines 12-15: Next, we are defining the subdomain "yourcompany", i.e. "yourcompany.org".
Lines 17-37: We define three organization units (ou): it, finance, and sales.
Lines 29+: We then add a use to the domain "yourcompany.org" and assign attributes with values. For example, "cn" means canonical name (or first name), "sn" means surname, and "mail" refers to that person's email address.
NOTE:
LDAP attributes differ depending on the type of environment you are using. For example, "userPassword" exists within OpenLDAP but not within Active Directory environments.
Understanding Basic LDAP Syntax
LDAP has a very specific structure for querying and has specific syntax. The following are common operators used in LDAP queries:
- "=" (equal to)
- & (logical and)
- | (logical or)
- ! (logical not)
- * (wildcard)
For example, if we wanted to query for anyone named "steve" in the LDAP structure above, our query would look something like this:
(cn=steve)
Or perhaps we want to search for anyone with a name that start with "s", we would employ a wildcard:
(cn=s*)
We could also search for anyone with a name that starts with "s" or "t" using the "|" operator:
(|(cn=s*)(cn=t*))
We may also use the "&" operator to require both fields to be correct. For example, if we wanted to search for any person with a name that starts with an "s" and last name that ends in a "d".
(&(cn=s*)(sn=*d))
Finally, we can combine all of these operators together to perform queries. For example, let's say we wanted to look for any person who's first name starts with "s" but their last name must start with a "d" or "r":
(&(cn=s*)(|(sn=d*) (sn=r*))
LDAP Injection Demo
Now that we have a better understanding, let's move on to the demonstration. Below can be seen an example of a company's "employee search form" website for "Your Company LLC":
Let's start by simply entering in some data and submit.
Our form submits the value "s" and the attribute "cn" (first name) via get queries (seen in url bar above) and returns to us a table that contains individuals with a first name that starts with "s".
We can view the source of this website to see the corresponding form fields and to get parameters found in the URL (see below).
We can see that the LDAP attributes only allow us to select certain fields. We can make this change in our URL or by editing the values in the developer console. This is typically easier to modify in the URL than it is in the developer console. Modifying URL parameters obviously will not work with POST requests so I typically prefer to edit these values in the developer console. Another method is to use a proxy like Burpsuite or ZAP to modify these values before being sent to the server.
Below you can see this inline modification being changed inline from "cn" to "password".
Then we simply click the submit button and voila! Below we can see that we manipulated the data being sent to allow us to retrieve users' password hashes. However this is still not LDAP injection, just poor coding practice to allow these attributes to be controlled in client-side code.
Now let's see if we can inject LDAP into our query and manipulate the results we see. For this demonstration, I will simply show the LDAP statement pre-injection (see below). Note that it is unlikely during a penetration test that you will have the source code to see these statements. In these situations, fuzzing and reconnaissance may have to be employed to successfully leverage LDAP injection.
As we can see, the developer did not provide any sort of input validation or filtering on values being passed by the user before concatenation. Like with SQLI, string concatenation for queries is dangerous. Most web frameworks with LDAP libraries also provide a proven method to escape and/or filter user supplied input before being queried. These methods/functions should always be leveraged if LDAP is being used when integrating with directory databases.
In the query above, a few things are happening:
- Our statement begins with a logical OR "|" which wraps two more sub-statements. If either match an object, then that object will return the desired attribute along with "cn","sn", and "ou".
- In the first sub-statement we have, (&(<supplied attribute>=<search term>)(department=finance)). This means that we can search for an object with any attribute and value we want BUT that object must also belong to the finance department.
- In the second sub-statement we have, (&(<supplied attribute>=<search term>)(department=sales)). This means that we can search for an object with any attribute and value we want BUT that object must also belong to the sales department.
- By combining these two together, the developer is trying to restrict the search query to user objects that belong to the finance OR sales departments only.
While stealing credentials for sales or finance employees can be incredibly valuable, gaining credentials for domain admins is even better. So, let's craft an injection that takes advantage of this poorly crafted query. For example, after changing the desired LDAP attribute in the URL from "cn" to "password" we could inject LDAP syntax as our search term:
))(department=it)(|(cn=
This would change our query from:
To:
This statement now queries our directory for any object that:
1. has any value password;
OR
2. belongs to the "IT" department;
OR
3. has any canonical name OR is in the finance department;
OR
4. has any canonical name OR is in the sales department.
That's a lot of "OR"s which are better than "AND"s when we want to inject LDAP.
Let's try this new query out:
Awesome! That did the trick and we now have domain credentials. As we can see, manipulating the attribute and injecting the right LDAP syntax unlocked our ability to query whatever we want from the directory.
Methods of Prevention:
- Always use the functions provided by your framework to properly validate, filter or escape user-supplied input.
- Do not allow users to specify attribute values client-side. Use store values or functions server-side that can be specified by the user.
- Format your queries better (along with other prevention methods) to prevent manipulation. For example, if the statement above had been changed to
it would have been considerably more difficult to break out of the finance or sales departments. Once again, proper query structure is not sufficient protection by itself.
- If the framework in question does not provide a method to validate, filter, or escape these values, try your best to filter user supplied input via regex, stored procedures, or some other method of input validation. However, custom methods should only be leveraged as a last resort and should be validated by other developers and/or security professionals for accuracy.
Other Notes:
- It is unlikely that during a penetration test, you will be provided source code and information about the directory database. As such, proper reconnaissance and fuzzing is key.
- You may or may not have access to manipulate attributes. In that case, you may be limited to injecting and only retrieving information from the predefined attributes.
- LDAP can also be used to update or delete a directory database, so BE CAREFUL during a penetration test.
- LDAP injection can also be used to bypass authentication. Check out below for a link to an OWASP article detailing this.
Useful Links On LDAP:
- LDAP injection Authentication Bypass: https://www.owasp.org/index.php/Testing_for_LDAP_Injection_(OTG-INPVAL-006)
- OpenLDAP attributes: http://www.zytrax.com/books/ldap/ape/.
- Ldap Query basics: https://technet.microsoft.com/en-us/library/aa996205(v=exchg.65).aspx
- Active Directory Attributes: https://msdn.microsoft.com/en-us/library/ms675090(v=vs.85).aspx