Experts Blog

Granularize Your Active Directory Reconnaissance Game Part 2
June 15, 2022
Matthew Creel

Last month Fortalice open-sourced [BOFHound](, an offline BloodHound ingestor for raw ldapsearch results. Along with BOFHound, we released a companion tool for it, [pyldapsearch](, and submitted a [pull request]( to TrustedSec's CS-Situational-Awareness-BOF modifying the ldapsearch BOF to include the nTSecurityDescriptor attribute. Adam Brown ([@coffeegist]( wrote a [post]( accompanying the release, which covered much of the tool's background, including blue team strategies for detecting BloodHound useful and the red team's reversion to more manual LDAP querying techniques. This blog will serve as a follow-up to that post, covering some usage strategies for ldapsearch + BOFHound and going into the updates that were recently pushed to BOFHound in version 0.1.0.

## ACL Mapping
The initial release of BOFHound aimed to aid in visualizing manually queried LDAP data by parsing those results so that group memberships (`MemberOf` edges) and ACL relationships (edges such as `GenericAll`, `Owns`, `ForceChangePassword`) could be imported into BloodHound.

Let's say your goal is to simply find abuse avenues over your target domain - you can start by issuing a simple LDAP query (using pyldapsearch or the ldapsearch BOF) with the filter `'(objectClass=domain)'`. Next, we'll run BOFHound over the log file and parse that single domain object:

Several JSON files will be written, which we'll upload to BloodHound. Locating the domain object in the BloodHound GUI, we can head to the *Inbound Control Rights* section and click the number associated with *First Degree Controllers*. This will us a graph that looks something like this, where we can see objects (ones we haven't queried) by their SIDs and their rights over the domain object.

With this as a starting point towards our goal, we've got more objects to query. Looking at those SIDs, I know `S-1-5-32-544` is the Administrators group, and the SIDs ending in 512, 516, and 519 are Domain Controllers, Domain Admins, and Enterprise Admins. I'm not sure what the SID ending in 498 is off the top of my head and I also know the SID ending in 1111 belongs to an object which no longer exists in this domain. Accordingly, I'll run some more LDAP queries to gather the objects in our result set:

'(sAMAccountName=Domain Admins)'
'(sAMAccountName=Enterprise Admins)'
'(sAMAccountName=Domain Controllers)'

Rerun BOFHound, load the output into BloodHound and now the graph is looking more filled in. Additionally, if we now click on one of the group objects we can see the distinguished names of members:

We can use this information to spider out further and start revealing *Unrolled Controllers*, or objects with nested group memberships granting nth-degree object control over the domain. My example domain isn't heavily populated so member objects of these groups are minimal. In client environments, it'd likely be tedious to query each manually by distinguished name, but the good news is you have options. Option 1 is to query with a filter on the `memberOf` field, like this: `'(memberOf=CN=Administrators,CN=Builtin,DC=ez,DC=lab)'`. This will return all *direct* members of the Administrators group. Option 2 is to query all *nested* members of the target group by using a specific LDAPOID, like this: `'(memberOf:1.2.840.113556.1.4.1941:CN=Administrators,CN=Builtin,DC=ez,DC=lab)'`. Something to consider with both of those options is that the `memberOf` attribute is not indexed by Active Directory, meaning the query is likely to visit a high number of LDAP objects, even if few are ultimately relevant. This is one of the detection strategies mentioned in the original BOFHound post. In environments where this is a detection consideration, I'll sometimes just use a bash loop or `xargs` to individually query the distinguished names with a small sleep between each query (if using the ldapsearch BOF, this probably requires some aggressor scripting). However, in my small lab I'll just query the members of these groups by sAMAccountName:


Rerun BOFHound, reimport to BloodHound and we can now see the unrolled controllers related to the target domain:

At this point, you can rinse and repeat what we've done to keep enumerating layers of attack paths. We could start exploring any unknown objects that pop up with the *Transitive Controllers* query on the domain, but we've also parsed ACLs for several users and groups which we could start to hunt paths to individually.

In practice, the approach demonstrated here - one or two LDAP queries followed by BOFHound and importing - is fairly slow, so you may choose to run larger sets of queries or more aggressive queries before each BOFHound pass. However, the approach here does demonstrate that as the operator, you are in total control over how granular you want AD reconnaissance to be, all while maintaining the ability to utilize BloodHound.

## GPO and OU Mapping
All of that was possible in the original release so what's new in BOFHound 0.1.0? In this release, we've added support for OU and GPO objects, as well as parsing out domain trust relationships (those can be queried with `'(objectClass=trustedDomain)'`). Targeted enumeration of OUs and linked GPOs will work similarly to the groups and group members from above. As an example, in my lab, I'll query all OUs, but if you're targeting some specific subset of machines (i.e., Citrix servers), you could try a query filter similar to `'(&(objectClass=organizationalUnit)(|(ou=*citrix*)(ou=*ctx*)))'`.

The *gpLink* attribute contains the distinguished names of associated group policy objects. GPOs can also be linked directory to domain objects:

Now we query for the GPOs linked to the specific OUs we're interested in:


Run BOFHound to collect the data from our logs, upload to BloodHound and we can see how GPOs filter down to OUs and other objects.

If we're hunting for modifiable GPOs, we can also view the inbound controllers and start down the ACL path again:

## Wrapping Up
The majority of the LDAP query filters used as examples throughout this post are very simple - mostly consisting of a sole condition to return a single object of interest. This is sufficient for populating targeted ACL relationships, however, if you're looking for slightly more wide-ranging queries to help populate your BloodHound database, you'll want to branch out from this structure. Two great resources to help you get started with manual LDAP searching can be found [here]( by Hope Walker ([@Icemoonhsv]( and [here]( by Rob Fuller ([@mubix](

For additional information on Fortalice Solutions' service offerings, contact the team via email at