When performing internal tests, it's common to use LDAP (Lightweight Directory Access Protocol), but its true purpose, utility, and power are often misunderstood. And since our motto is “Understand before you attack,” we'll explore what LDAP is, its origins, the earlier version, its nuances, and much more.
What are DAP and LDAP?
DAP (Directory Access Protocol) was a protocol created based on the X.500 standard (which we'll discuss in the Directories section) and the OSI model. As we know, the OSI model is not widely used in practice, with TCP/IP being the dominant standard. Because of this, DAP faced performance issues, as it was slow and resource-heavy.
To address these limitations, LDAP emerged, with the term Lightweight added to its name. It retained the use of X.500 for directory structure but switched to TCP/IP as its underlying protocol. This made it lighter, faster, and more efficient than its predecessor — and it's still widely used today.
What are directories?
As mentioned earlier, LDAP uses the X.500 standard to structure its directories. This standard was developed by the ITU-T (International Telecommunication Union – Telecommunication Standardization Sector). The core idea is that there is a single DIT (Directory Information Tree), where all entries are organized — such as groups, users, computers, organizational units (OUs), and more. Each entry contains attributes and the corresponding values for the object it represents.
In this image, we can see how the directory is structured: it resembles a tree, with hierarchy as its foundation. DC=com represents the root of this directory, and everything beneath it is considered its “children.” Using the user D3v as an example, we can see that this object's "parents" are, in ascending order, Pentest, hakai, and com.
A common element in this type of structure is the DN (Distinguished Name), which allows us to identify the full path to an object within the directory. For example: DN: DC=com,DC=hakai,OU=Red Team,CN=Oliveira. With this DN, we know exactly where the user Oliveira is located within the tree.
Now that we understand what LDAP is and how a directory works, a natural question might arise:
“What can we actually do with this directory and its objects?”. To answer that, we need to understand LDAP operations.
LDAP Operations
There are several operations that can be performed with LDAP, as defined in RFC 4511, specifically in section 4.1.1 (Message Envelope).
In this post, we will cover the main LDAP operations: Bind, Add, Delete, Modify, and Search.
Bind Operation
The Bind operation is used for user authentication. There are two main types of authentication: Simple and SASL (Simple Authentication and Security Layer). Authentication is detailed in RFC 4511, section 4.2 (Bind Operation), where you can see exactly how this operation is specified.
Simple Authentication, as the name suggests, is used for basic authentication using a username and password.
ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "OU=Pentest,DC=hakai,DC=com"Using the ldapsearch tool with the -x option (which enables Simple Authentication) and passing the -D flag with the DN of the user to be used — in this case, we can use [email protected] or CN=maxin,OU=Pentest,DC=hakai,DC=com — we establish a connection with simple authentication.
From this point onward, and for the remainder of this post, we will use Wireshark to analyze the communication. In the following example, we will see how authentication is performed.
Unlike Simple Authentication, SASL is used for authentication through external protocols, such as GSSAPI, which is employed in Kerberos authentication.
To use an external protocol in ldapsearch, we use the -Y option.
ldapsearch -Y GSSAPI -H ldap://dc01.hakai.com -b "dc=hakai,dc=com"We can observe that, compared to Simple Authentication, communication using an external protocol is much more complex and requires sending a larger amount of information.
Add Operation
The Add operation is used to add a new entry to the directory. We can include users, groups, computers, and other objects, depending on the permissions of the user performing the action.
In this example, we will add a new machine to the domain. To do this, it's necessary to create an LDIF (LDAP Data Interchange Format) file, which will be used by the ldapadd command.
After creating the LDIF file, we can execute ldapadd, using a user with administrative privileges to perform the operation.
Modify Operation
The Modify operation is used to change entries in the directory. We can alter LDAP objects depending on the permissions of the user performing the action. Just like with the Add operation, this modification is also done through an LDIF file.
This operation is very important but not commonly used. If your user has permission over another user or belongs to a privileged group, it's possible to use the modify operation to alter entries in the LDAP directory. For example, if an attacker gains access to an account that belongs to the Account Operators group, they could use this operation to add users to other groups in Active Directory.
In this example, we will add the user Oliveira to the Administrators group.
We’ll use the ldapmodify tool to perform this action, passing in the previously created LDIF file.
Delete Operation
The Delete operation allows you to remove an entry from the directory. Unlike the previous operations, Delete does not require an LDIF file, as it is a direct action where you specify the DN of the entry to be removed.
In this example, we will delete the computer that was previously created using the Add operation.
Search Operation
The Search operation is the most commonly used in LDAP, allowing you to query all information within Active Directory.
To understand the Search operation, we need to break down the elements required to construct it. In section 4.5.1 (Search Request) of RFC 4511, we can see exactly how it is defined.
This operation is divided into several components: baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, and attributes. In this post, we’ll focus on the elements baseObject, scope, filter, and attributes.
Base Object & Scope
The base object defines the point in the “tree” from which the search will begin. For example, if we define the base object as OU=Pentest,DC=hakai,DC=com, all the information collected will be from the Pentest organizational unit (OU). In this case, the search will not return data related to other OUs.
Usually, the baseObject is defined at the top of the "tree", such as DC=hakai,DC=com, so that the search retrieves all possible information.
The scope, on the other hand, determines how deep the search goes from the baseObject — which is why these two concepts complement each other. As shown in the previous image, the scope has three options:
- baseObject (0)
- singleLevel (1)
- wholeSubtree (2)
Each of these options defines the depth level of the search. To better illustrate this, see the following image:
In this case, we define the baseObject as DC=hakai,DC=com.
If we set the scope to baseObject (highlighted in red), the scope is limited precisely to the defined base point. When choosing singleLevel (highlighted in blue), the scope covers only one level below the baseObject. The wholeSubtree option (highlighted in green) allows you to view the entire tree starting from the defined base.
The most commonly used scope option is wholeSubtree, but it’s important to be aware of all available options. In this example, we’ll use ldapsearch again, without explicitly defining the scope:
ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "objectClass=user" cnWe can observe in Wireshark that the data being sent matches exactly what is specified in the RFC.
In the ldapsearch command, the -b option defines the baseObject, and the -s option defines the scope. Since we didn’t specify a value for the scope, the default wholeSubtree is used, as shown in the image.
Attributes & Filter
Attributes
In this section, we'll start by talking about attributes, which form the foundation for understanding filters in LDAP.
All objects in Active Directory have attributes. To view these attributes, we can perform a simple search using ldapsearch.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn=maxin"Below, we present some of the attributes of the object maxin. Based on these attributes, we can apply what is called Attribute Selection to filter which information we want to retrieve. It’s important to note that in the ldapsearch command, attribute selection is defined at the end of the command.
By capturing this LDAP query in Wireshark, we can see the selected attributes.
It’s interesting to understand what happens when we perform a simple LDAP search without explicitly defining a Filter or Attribute Selection.
ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com"If no filter is specified, ldapsearch will default to the filter objectClass=*. This means the search will return all object classes present in Active Directory — essentially all objects. The attributes field, when set to 0, is interpreted as *, which causes all attributes of the objects matched by the filter to be returned.
Now that we understand the basics of Attribute Selection, we can move on to Filters. In search operations, the filter is one of the most important elements, as it determines exactly what kind of information will be collected. And this filtering is based on the attributes of the objects.
Filter
By consulting RFC 4511 again, specifically the section related to filters, we find something quite interesting.
The RFC defines various types of filters, including the so-called Boolean operators — AND (0), OR (1), and NOT (2) — which we will discuss later in this post. In addition to those, there are other filters such as:
- equalityMatch (3)
- substrings (4)
- greaterOrEqual (5)
- lessOrEqual (6)
- and others.
To better understand how each type of filter works, let’s start with one of the most basic: equalityMatch (3).
In the following example, we will filter all objects whose CN attribute is equal to "maxin". Since there is only one object with this name, the result will be a single entry — the maxin object itself.
ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn=maxin"By capturing this operation in Wireshark, we can see exactly what the RFC describes: the use of equalityMatch (3), the attributeDesc field (which indicates the attribute being filtered), and the assertionValue (the value defined in the query).
Another very useful filter type is substrings (4). A practical example would be:cn=ad*. In this case, we’re using the CN attribute with a wildcard (*) to search for all objects whose Common Name (CN) starts with “ad”. Although this example uses the CN attribute, the substrings filter can be applied to many other attributes depending on your needs.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn=ad*" cnThis filter will return all objects whose CN starts with “ad”. It’s important to note that Windows (and consequently Active Directory) is case-insensitive, meaning it does not differentiate between uppercase and lowercase letters. That’s why names with uppercase letters will also appear in the results.
By capturing this operation in Wireshark, we can clearly see how the search was executed.
There are two additional filters worth highlighting: greaterOrEqual (5) and lessOrEqual (6). Let’s understand how they work and how they can be used in more advanced queries.
Both follow the same usage pattern — you need to provide a value such as a number, letter, or symbol.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn>=a" cnWhat exactly is greaterOrEqual doing in this case? When we use this filter with the letter “a”, LDAP converts the character to its decimal value in the ASCII table — which is 97. From there, it returns all objects whose attribute values start from 97 onward — in other words: a, b (98), c (99), d (100), and so on. The smaller the value provided (in ASCII terms), the broader the search scope becomes.
In the next example, we used the symbol “!”, which corresponds to 33 in the ASCII table. This query returned 711 entries, compared to the previous example using the letter “a” (ASCII value 97), which returned only 455 entries. This demonstrates how lower ASCII values significantly increase the search scope.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn>=!" cnUnderstanding how greaterOrEqual works makes the use of lessOrEqual a logical next step, since both follow the same operational pattern. The "trick" now is to reverse the logic: use a letter or symbol with a high ASCII value to limit the results — in other words, go the opposite way and filter only objects with values less than or equal to the one specified.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn<=a" cnSearch Extensions
As mentioned earlier, ldapsearch does not return all available attributes of objects by default. For example, the ntSecurityDescriptor attribute is extremely important because it contains information about DACLs and SACLs, which define permissions and auditing. However, if we try to query it directly, as shown in the example below:
"cn=maxin" ntSecurityDescriptorWe won’t get any results for this attribute. But that doesn’t mean it can’t be accessed.
To retrieve it, we use a feature called Search Extensions in ldapsearch, enabled with the -E flag.
-E '1.2.840.113556.1.4.801=::MAMCAQc=' 'cn=maxin' ntSecurityDescriptorLet’s break it down to understand why this query works. The Search Extensions option accepts only OIDs (Object Identifiers). The OID 1.2.840.113556.1.4.801 is interpreted as an extension called SD_FLAGS.
SD_FLAGS defines which parts of the ntSecurityDescriptor will be returned. The main components and their corresponding values are:
- DACL (Discretionary Access Control List): 1
- SACL (System Access Control List): 2
- Owner: 4
- Group: 8
If we want to retrieve DACL, SACL, and Owner, we need to add the values: 1 + 2 + 4 = 7. The value MAMCAQc= seen in the extension is a base64-encoded blob that represents this number (7), encoded in the ASN.1 structure required by SD_FLAGS.
Boolean Operators
Boolean operations in LDAP allow the construction of more complex and specific filters using the AND, OR, and NOT operators. Let’s better understand how each of them works in practice.
The AND operation is represented by the symbol &. Like the OR operation (which we will see next), it requires combining two or more conditions, each enclosed in parentheses. The structure looks like this: &(condition1)(condition2).
In this example, we perform a simple check. We are verifying if the object belongs to the user class and if the CN equals maxin. Both conditions must be true for the object to be returned.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" '(&(objectClass=user)(cn=maxin))'We can improve this filter by using what we learned in the previous section with the greaterOrEqual operator. We are checking all objects of the user class that match the search filter cn>=!.
ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" '(&(objectClass=user)(cn>=!))'The OR operation follows the same structure as AND, but with a fundamental difference: it is enough for one of the conditions to be true for the object to be returned.
To use OR, we use the symbol "|", as follows: |(condition1)(condition2). Normally, OR is combined with other boolean operations to create more precise filters. For example:
'(&(objectClass=user)(|(cn=maxin)(cn=luriel)))'In this case, we use an AND in conjunction with an OR. Notice that, after the condition (&(objectClass=user), we open another parenthesis — this contains the OR operation, with two internal conditions. Here, since both conditions within the OR are true (the CNs maxin and Luriel exist) and the AND condition is also satisfied, both objects are returned.
When analyzing this search in Wireshark, we can observe an interesting pattern: the filter starts with AND (0), and inside it there is an OR (1) — exactly as structured in the command. This clearly shows how the filter is interpreted by LDAP.
The NOT operation, represented by the symbol "!", is the last boolean operation available in LDAP. As the name suggests, this operation negates the given condition. Unlike AND and OR, NOT requires only a single condition, meaning only one pair of parentheses is necessary.
In this command, we are searching for all objects of the user class that do not have CN=maxin. The result will be a list of all users except the maxin object.
'(&(objectClass=user)(!(cn=maxin)))' cnIn the image above, we can clearly see in Wireshark the presence of the AND operation (0) and, within it, the NOT operation (2) applied to the condition.
These were simple examples to demonstrate the use of Boolean Operators in LDAP. Despite the simplicity of the cases presented, these operations have a much broader application, especially when integrated into web-based authentication mechanisms, where they can be used to build or manipulate access logic based on LDAP filters.
Conclusion
When we come across an Active Directory environment, understanding how LDAP works is essential. Even if we don’t use it directly, it is almost always working behind the scenes. That’s why knowing the protocol, understanding how it operates, and how to extract information consciously is extremely important. Widely used tools like BloodHound, for example, make extensive use of LDAP to collect data and perform correlations — such as the ntSecurityDescriptor attribute mentioned earlier, which allows identifying permissions between objects and what a user can or cannot do in the domain. Another example is Certipy, used in the reconnaissance and exploitation of vulnerabilities in Active Directory Certificate Services (AD CS). Like BloodHound, Certipy also uses LDAP to search for and structure critical information.
These examples clearly demonstrate how powerful the protocol is. Although commonly remembered only for its search function, LDAP also allows adding, modifying, and deleting entries in the directory — functionalities which, if misused or exploited, can seriously compromise an environment.
We always emphasize the importance of understanding how systems — protocols, services, etc. — truly work before trying to attack them. It’s no use if we don’t understand what’s happening and just attempt to attack. In this scenario, we end up simply running tools without comprehending what’s really going on — and most likely without achieving any meaningful results. Understand first, then attack!
I hope this blog post has helped you, in some way, to better understand the LDAP protocol and everything that can really be done with it. Additionally, this post was also meant to show how curiosity and the desire to understand can lead to significant comprehension of any topic. I hope this demonstration serves as fuel for everyone reading and encourages you to do the same.