389,636 - LDAP
What is LDAP?
LDAP stands for Lightweight Directory Access Protocol and it is used for accessing various directory services, such as the Active Directory (AD) service in Windows. It uses ports 389 (unencrypted) and 636 (encrypted). LDAP is the language that systems use to query AD, similar to how web applications use HTTP to speak to web servers.

A Domain Controller (DC) can also be granted the Global Catalog role which is an LDAP-compliant directory consisting of a partial representation of every object from every domain within the forest. This is available by default on ports 3268 (unencrypted) and 3269 (encrypted).
Authentication
All domain users can authenticate with the LDAP server.
LDAP authenticates credentials against AD using a BIND operation. This operation establishes the authentication state for an LDAP session. There are two main types of LDAP authentication:
Simple → anonymous, unauthenticated, and username/password authentication. In this method, the client directly provides credentials to the LDAP server.
Simple Authentication and Security Layer (SASL) → allows LDAP to use external authentication services, such as Kerberos, rather than sending credentials directly. The client first authenticates with another system and then uses that trusted identity to bind to LDAP.
SASL/GSSAPI → a common SASL mechanism that integrates LDAP with Kerberos. If an attacker compromises a valid Kerberos ticket, they may be able to bind to LDAP without knowing the user’s password!
Tools like windapsearch and ldapsearch-ad can be used to test authentication:
LDAP Syntax
LDAP queries must have one or more criteria and they are written with Polish notation, i.e., the operators are placed in front of the criteria (aka operands). More details about operators used can be found in the Search Filters section.
Equal to
(attribute=123)
(&(objectclass=user)(displayName=Smith))
Not equal to
(!(attribute=123))
(!objectClass=group)
Present
(attribute=\*)
(department=\*)
Not present
(!(attribute=\*))
(!homeDirectory=\*)
Greater than
(attribute>123)
(maxStorage>100000)
Less than
(attribute<123)
(maxStorage<100000)
Approximate match
(attribute~=123)
(sAMAccountName~=Jason)
Wildcards
(attribute=\*A)
(givenName=\*Sam)
Object Identifiers (OIDs)
In LDAP, an OID is a globally unique, hierarchical dotted-decimal number used to identify schema elements such as:
Object classes (users →
1.2.840.113556.1.5.9)Attributes (
sAMAccountName→1.2.840.113556.1.4.221)
LDAP servers rely on OIDs internally, while humans typically use friendly names. This is because names can change, collide, or differ between vendors, whereas OIDs are stable and never change. This is especially important in environments like AD, where consistent schema identification is critical.
Matching Rule OIDs
A matching rule OID tells LDAP how to compare an attribute’s value with a search filter. This is required when a simple equality comparison (=) is not sufficient, such as bitwise flag checks, recursive group membership, and special string or binary comparisons.
Let's go over an example of a bitwise matching rule. Some attributes (e.g. userAccountControl) store multiple flags inside a single integer value. A normal equality comparison cannot isolate a specific flag, so a bitwise matching rule is required. For instance, if we are interested in enumerating standard user accounts (512) with the ACCOUNTDISABLE flag set (2), we need to target the latter:
LDAP evaluates the above as:
which translates to “Return all objects where the disabled account flag is set.” For example:
represents multiple account properties at the same time. In its binary representation (1000000010) each bit has a specific meaning, and several bits can be set simultaneously. The resulting decimal number 514 is simply the sum of these bits:
In essence, a bitwise check asks “Is this specific bit set?” rather than checking if the full number equals a value. A normal equality check would fail in this case:
But the bitwise check succeeds:
The key takeaway is that matching rule OIDs exist because some comparisons require special logic that cannot be expressed with simple equality (=). We can enumerate and translate UAC flag values as shown in the Tools section.
Search Filters
The Active Directory PowerShell module, along with its Filter and LDAPFilter params, can be used to enumerate an AD environment via LDAP. Tools like PowerView use those filters under the hood.
Equality
(cn=Alice)
"Name -eq 'Alice'"
Not equal
(!(cn=Alice))
"Name -ne 'Alice'"
Greater than
(logonCount>=5)
"logonCount -ge 5"
Less than
(logonCount<=5)
"logonCount -le 5"
Wildcard
(cn=Alice*)
"Name -like 'Alice*'"
Ends with
(cn=*Alice)
"Name -like '*Alice'"
Contains
(cn=*Alice*)
"Name -like '*Alice*'"
Approximate match
(sn~=Smit)
"Surname -like 'Smit*'"
Presence
(mail=*)
"mail -like '*'"
Logical AND
(&(objectClass=user)(cn=Alice))
"ObjectClass -eq 'user' -and Name -eq 'Alice'"
Logical OR
(|(cn=Alice)(cn=Bob))
"Name -eq 'Alice' -or Name -eq 'Bob'"
Logical NOT
(!(cn=Alice))
"Name -notlike 'Alice'" or "Name -ne 'Alice'"
Boolean
(enabled=TRUE)
"Enabled -eq \$true"
LDAP Queries
The LDAPFilter uses standard LDAP query syntax to search AD.
Because some symbols are used as operands, if we need to use them literally within a query, they need to be escaped.
*
\2a
(
\28
)
\29
\
\5c
NULL
\00
AD Queries
The Filter parameter uses PowerShell expression syntax (SQL-like), which is internally converted into an LDAP query.
Search Base & Scope
These parameters are essential for controlling where and how deeply AD queries are performed, because without limiting the search base and scope, a query can potentially return extremely large result sets, which may impact performance and consume unnecessary resources:
The
SearchBaseparameter specifies the AD path under which the search should be performed, typically using the distinguished name (DN) of an organizational unit (OU) or container.The
SearchScopeparameter determines the depth of the search relative to theSearchBase, allowing queries to be targeted to a single object, a container, or a container and all of its subcontainers. It has three levels:Base→ searches only the specified object (the exact DN) and does not include any child objects.OneLevel→ searches for objects directly within the container, but does not recurse into sub-containers.SubTree→ searches for objects within the container and all of its child containers, recursively.
In the below example, if we define our SearchBase to the Employees OU, the Get-ADUser will fetch:
Base→ nothing (just the DN itself, i.e.,OU=Employees,DC=example,DC=com, which is not a user)OneLevel→Amelia Matthews(the only user directly under theEmployeescontainer)SubTree→ all users in all sub-containers (all users and subcontainers underEmployees)

Recursive Match
An interesting case when enumerating via LDAP, is when we need to do a recursive match, e.g. nested group memberships. In the example below:
harry.jonesis a member ofSecurity OperationsSecurity Operationsis a member ofDomain Admins
Using the below command does not reveal the above relationship:
However, we can reveal it using the RecursiveMatch parameter or a matching OID rule:
Tools
Built-in
Besides the PS AD module, dsquery, Get-WmiObject, and adsisearcher can be also used:
Third-Party
UAC Conversion
UAC values can be converted into flags using the AD PS module and the Convert-UserAccountControlValues.ps1 script, as well as PowerView:
Last updated