Skip to content

Tools, Techniques, and Grimmie?: Experimenting w/ Offensive ADSI

In this installation of the “Tool, Techniques, and Grimmie?” series, I’ll document my adventures in exploring ADSI. ADSI is a set of interfaces allowing administrators to query information on an Active Directory environment. While there are methods included to enumerate potential attack vectors in a network, these potential vulnerabilities will not be exploited or covered here (though I will include resources on these attack vectors), the goal of this is to solely showcase domain enumeration using a method rarely covered.

A Preamble…of sorts

I was casually scrolling past Twitter one evening and came across this tweet, which got me interested in this “adsisearcher” thing for quite a few reasons. More often than not, whenever I hop on Google to search for ways to enumerate active directory once on a windows box, the vast majority of articles (if not nearly all of them) heavily reference PowerView. This gave me the impression that many were indeed highly dependant on PV (I will refer to PowerView as PV for the remainder of this article) as this tweet suggests but there was another thing that piqued my interest: the words “built-in”. This means that adsisearcher is naturally present in windows environments, meaning whatever this is, it has the capability to enumerate Active Directory without having to drop anything on a box (or even load anything into memory, more on this later). Allow me to translate that to English: An attacker can use whatever this adsisearcher thing is to enumerate a domain without the use of external tooling. Now that’s definitely something that warrants my further research into the matter.

Now there is another reason as to why I am writing this article. During my “research”, I found that there is little to no documentation on the topic with the exception of scattered MS docs (and we all know how great MS docs can be sometimes) and posts on random developer forums showcasing specifics tasks, which doesn’t really give us much to go off of. And I certainly found close to nothing on using this technology offensively. My aim with this article is not only to showcase the offensive capabilities of DirectoryServices, but also to serve as at least some sort of coherent documentation containing more information than just how to filter a single type of object and that’s it.

OK So What is this Adsisearcher Thing?

I’ve referenced adsiseacher quite a few times in the introduction though I’ve yet to actually explain what it is. adsisearcher is what’s called a type accelerator, which is fancy talk for aliases (in a sense) that points to a .NET class.

adsisearcher points to the[DirectoryServices.DirectorySearcher] class. Aside from the adsisearcher type accelerator, we’ll also frequent another type accelerator: ADSI (which points to [DirectoryServices.DirectoryEntry]). A list of type accelerators can be found here.

adsisearcher and adsi (recall PS is case insensitive) allow us to use LDAP to query AD DS commonly used by sysadmins, making this a rather OPSEC considerate option to enumerate an AD environment and seems to be what PV uses under the hood, or at least previous versions.

ADSI vs PV

“If this is what PV uses under the hood, why should I care about learning how to use ADSI if there is already a popular tool out there making use of it?” You may be wondering…or maybe not, I’m not telepathic (would be cool though). We’ve already established this is what PV runs under the hood (or older versions at least) so it would be safe to assume that PV is already fairly OPSEC considerate. Yeah, you’re right. An IoC stripped version of PV would do just fine. It’s an awesome tool that automates this entire process which everyone raves on about, and it is indeed an amazing tool, I can’t take that away from it, nor will I attempt to do so. The focus of this article isn’t to steer anyone away from PV, rather to introduce another option as well as present the fruits of my messing around with the DirectoryServices namespace.

Knowing how to leverage ADSI as an attacker serves many benefits. All of the commands showcased were executed in ConstrainedLanguage mode with Windows Defender turned on and I ran into no issues with either AMSI or CLM flagging or restricting any of my capabilities. A known method of bypassing CLM is to run PSv2, which also allows us to leverage ADSI without a hitch. There is also the aforementioned detail stating how this namespace is present on every windows version. All these things make ADSI a great tool to have in the box.

ADSI and adsisearcher…what’s the difference?

Before moving onto the fun stuff, I should clearly differentiate ADSI and adsisearcher as I’ve mentioned both in a somewhat interchangeable manner. As previously stated, they are both type accelerators and thus point to different classes. We can use adsisearcher to return LDAP queries corresponding to an object in the domain as well as a bit of information on the object and gain a general “lay of the land” in a domain. We’ll use ADSI to query more details (namely permissions, which I will go into more detail later) on specific objects using the LDAP queries returned from adsisearcher.

Though I think I’ve rambled enough, let’s get to it shall we? Before diving into it, I mentioned we can leverage the ADSI suite while in constrained mode (though there are some limitations of course, mainly arising when having to call out to external libraries. I’d also like to mention that I will not be going into the mechanics of ConstrainedLanguageMode here. That is out of scope of this article and I recommend reading further into what it is and what it means for attackers as well as bypass techniques). Everything done going forward will be within a PS session in CLM. Oh yeah, and also not in PSv2 as that would defeat the purpose of trying to stay within CLM. Though I may break out of CLM or run certain things in another PS process to showcase how to parse output or something along those lines, which I will warn about if/when we get to that point.

Searching w/ adsisearcher

First things first, we’ll have to create an adsisearcher object (and verify it is the same as the DirectorySearcher class).

Looks about right, moving on. Now that we have the adsisearcher object created, let’s take a look at what options we got. adsisearcher doesn’t have too many properties and we aren’t going to be using too many of em.

Out of these properties, we’ll focus mainly on Filter and touch on PropertiesToLoad (which unfortunately requires us to break out of CLM but it’s nothing a little PS property parsing magic can’t solve, though I’m leaving that part up to you, the reader. It’s a really nice feature to be aware of which is why I included it despite the fact that it’s unavailable in CLM) and SearchRoot. Details on the other various properties not covered in this article can be found here.

Filter will allow us to set filters using LDAP filtering syntax, which we’ll make liberal use of later on.SearchRoot can be used to return the root domain and query basic information such as children as well as certain other properties. The last property we’ll make use of from the adsisearcher object is PropertiesToLoad, which allows for more granular control over output by giving the option to return only the specified properties. If next blank, then all properties are returned. We’ll focus heavily on the filter property for adsisearcher though I will touch on both SearchRoot and PropertiesToLoad, I encourage y’all to play around with their options/sub-properties in your own labs.

Searching for the Root

Ok, that was kinda lame…moving on. The SearchRoot property can be used to query data about the root domain and tells us where we start searching from. From this property, we can query information such as child objects of the root domain, DC name, etc. As we can see, running $searcher.SearchRoot without any properties, like so

We can also query the first five child objects of the domain specified in the SearchRoot property and the DC name with a little PS magic sprinkled in.

The LDAP sieve: Filter

We can use the Filter (PS is case insensitive, filter would also do the trick) to search for objects in the domain with specific properties ( In this section, I’ll cover just a few but in a later section I’ll showcase more properties used to enumerate certain objects in the domain) such as objectclass and admincount to gain information on objects in the domain. One thing to note before actually messing around with the filter property is there are two ways of returning the results of a search: either return the first instance of the search (FindOne()), or every instance of the search (FindAll()) (that I’ve found at least). My workaround for this is usingselect, which is an alias to the PS Select-String cmdlet.

Hence the name, we can use the objectclass property to filter out domain objects based on an object class, such as listing out all users or groups in a domain like so

The admincount property will return all objects with the value of admincount specified in the filter

Did Anyone Ask For a Specific…PropertyToLoad?

Corny subheaders aside, in the scenario we want only a specific property returned from a search, we can add that property to the PropertyToLoad…property (parent property? InfoSec nomenclature is fun).

Using the PropertyToLoad property, we can further refine our search by telling adsiseacher that we not only want to return objects with the user as the objectclass value, but also only return the admincount and cn properties from those resulting objects.

Now that we have a basic idea of the properties we’ll make use of with adsisearcher, let’s take a look at what we can do with ADSI.

So…ADSI?

ADSI as mentioned before is a type accelerator pointing to the DirectoryEntry class belonging to the DirectoryServices namespace. (ADSI is also used to refer to the collective set of COM objs used by admins to interface with and automate tasks in a domain env. More on that here. )This class allows us to make LDAP queries and retrieve more specific data about a domain object. We can use searcher from the previous section to fetch the LDAP query corresponding to a specific user. This is the point where we start running into some limitations within CLM.

Let’s create an ADSI object pointing to the user Aaron Tarolli and see what kind of information we can retrieve. We can do this by searching for the user’s canonical name and returning the Path property.

Now let’s take a look at what kind of data we can find on this user without going too far into it.

As we can see in the screenshot above, we can get the canonical name for this user, what groups they’re a member of, their object category and class, and the parent object (there’s more we can learn from ADSI but more on that later).

Sidenote about DirectoryServices

While I was messing around with the DirectoryEntry and DirectorySearcher classes, I came across a few other interesting classes from the DirectoryServices namespace. Namely the ActiveDirectory class. Note that this will not run within CLM, though I felt it was an interesting little tidbit to be aware of.

Within this class resides the Domain and Forest subclasses, which can be used to gain more information of the residing domain and forest respectively, more on these classes here.

Domain Enumeration w/ ADSI and adsisearcher!

Yes, we’ve finally gotten to the part where we get into actually enumerating a domain environment! You know what they say, “It’s not the destination, but the journey that counts!”…or something like that.

Now that we have a general idea of the capabilities of adsisearcher and ADSI, let’s see how we can use these features to enumerate a domain environment. This section will start off by showcasing how to query general information on the domain (i.e listing users, finding DA’s/OUs, identifying members of groups, etc.) and then go into enumerating potential vulnerabilities and misconfigured perms (though I won’t be going into how to exploit what we find, that’s beyond the scope of this article).

Instead of just rattling off commands and screenshots, I figured it’ll be more interesting to go through my methodology when enumerating a domain env and go into detail about the different kinds of filtering that will be used. My tradecraft may or may not be to your standards, please don’t @ me….or please do actually, always great to hear what others think and where I can improve.

Querying Users and Groups in a Domain

Let’s say we’re on an engagement and our phishing team just handed us a shell on the client’s environment. What’s do we do now? (After verifying the context we’re running as is in scope) The first step as soon as we drop into an unknown environment would be to figure out who we are, what kind of groups we’re a part of, what other groups are there on the domain, etc. We want to start by getting a basic lay of the land and see what we have to work with.

Dropping into a PS shell on a box prints out PS C:\Users\s.chisholm.grimsec. It may be safe to assume that grimsec is the name of the domain we’re dropped into (though this may not always be the case). Grepping out what we can assume is the domain’s name from the output of whoami /groups tells us the s.chisholm user is a member of the Sales group in the grimsec domain. Now that we know our current user is a member of the Sales group within this domain (meaning they are a domain user), let’s create an adsisearcher object and see what we can dig up about this user and then see what other users and groups there are in this environment.

We can get some quick info on this user object from the adsisearcher properties. Looks like this user is indeed a member of the grimsec domain and the DC of this domain is grimsec according to the adspath and distiguishedname properties. From a quick glance, looks like we have some general info on the user, some info on their position in the domain, how many times this user has logged in (they seem like quite the active user with 166 logins), and even some stuff about login info. Let’s see if we can parse those into something we can read a bit easier.

We’re able to parse them into something easily readable by throwing them into variables and then running them through the FromFileTime method. Notice how I added [0]to the end of each of the properties, this is because the properties are returned as a single index Collection and that doesn’t play nice with DataTime so we have to specify the first index to return the actual value and store it in this variable. Now that we have an idea of who our current user is, let’s take a look around the domain and see what other users and groups there are.

Here we’re returning all the user accounts (top) and groups (bottom) on the domain. To find all user accounts, we’re using the (&(objectclass=user)(givenname=*)) filter to return all user objects with a given name. The given name property is commonly used to specify the full name of a user within a domain.

To locate groups on the domain, we’ll use (samaccounttype=268435456) to return all the objects with the samaccounttype property, which identifies objects as a SAM_GROUP_OBJECT (A list to all the samaccounttype values can be found here). Using the (objectclass=group) filter here will return properties we don’t really care for.

Taking a look at the list of groups returned, the last four (Senior Management, IT Admins, Engineering, and Sales) don’t seem to be default groups. Let’s see if we can see which users we found in the previous query belong to which group (we already know our current user is a member of the Sales group, though let’s verify this).

One of the first few things we noticed after landing on the domain was that our current user is a member of the Sales group and we can see that we were right in that assumption when listing out the users of that group. Aside from now knowing who belongs to what group in the domain, we can also see that there is a single domain admin in this environment and it’s Aaron Adams, who is also a member of the Senior Management group (recall the DA is basically the head honcho of a domain so long as the domain is the only one in the env. In the case where a domain is merely a part of a larger forest, the Enterprise Admin will take this role).

Time for Some More General Domain Enum!

I’ve read through quite a few articles covering MSSQL servers in a domain env so let’s take a look and see if there is one present here.

Looks like there is indeed a MSSQL service running in the env, which can be found by searching for a canonical name along the lines of “mssql”. Another thing I’ve seen quite often in various talks is potentially dangerous information that we as attacks can leverage in places like account descriptions, maybe we can find something there? Only one way to find out! Let’s see if we can find something leaking a password maybe?

Would ya look at that, looks like this domain does have a user with a password listed in the description! For this search, I decided to opt for the samaccounttype filter in place of objectclass as it’s a bit more accurate and I noticed objectclassreturned a few false positives for some searches we’ll look at later on. The description property is also used to actually search through descriptions (notice the & telling the filter we want to search for more than one matching property).

Querying Password Info (Working w/ System.__ComObject values)

When doing something like password spraying, the more info about password policies pertaining to the domain, the better. So let’s see what we can dig up. We’ll start by creating an ADSI object pointing to the DC and see what properties relating to passwords we can find. First let’s query the DC and see what properties we have to work with.

Oh…this might be fun. Going down the list we have

  • max and min password age (min/maxPwdAge)
  • minimum password length (minPwdLength)
  • password propertes (more on this one here)
  • the number of recent passwords held onto (pwdHistoryLength)
  • for how long someone would be locked out (lockoutDuration)
  • how long before the lockoutThreshold is reset (lockoutObservationWindow)
  • after how many failed attempts is a user locked out (lockoutThreshold)

Taking a look at the values, we can see quite a few are returning {System.__ComObject} as the value…which may prove to be “fun”. After quite a bit of googling and reading around, turns out that this is an internal .NET type usually referencing an integer too large to be displayed (keyword in there: usually, this isn’t always the case). In order to actually view these values, we’re going to have to “cast” these values as Int64 values and thankfully there is a method out there that makes this rather easy for us. Let’s translate this stuff into something we can actually read. I would be using a PSCustomObject for this for the sake of organization and cleanliness…and all that, but CLM. Though we can still parse the output to look semi(?) clean by creating an object and passing a hashtable as an argument.

To translate the __ComObject values into something readable, we can use the ConvertLargeIntegerToInt64 method and pass it the object we want to convert then divide the output by -600000000 to get the true value. This is because the output of the method returns a signed long int(Int64) returning the value in nanoseconds. To convert nanoseconds to minutes, we divide the value by 600000000 to get minutes and make it negative to offset the negative value the method returns (notice how we multiplied this value by 1440 for the PwdAge properties to convert them to days). We then store this value into a variable and go down the list following this process for every one of the __ComObject values. After storing the values into variables, we can pass the object (PS pipeline ftw) to the Format-List cmdlet to print out the custom object as a pretty list.

So…What Else Can We Find? (Miscofigs maybe?)

Well I’m glad you asked, let’s see what else we can find that’s a little more “interesting”. How about we try searching for kerberoastable accounts within the domain?

Looks like we can kerberoast the mssql_svc account (recall we can identify kerberoastable users by searching for user accounts with some sort of SPN value). What about users we can target for AS-REPRoast attacks?

Looks like there are two potential users we can AS-REPRoast in this domain. While we’re at it, let’s see if we can find any machines we can target for unconstrained delegation!

Looks like we do have a potential target for an unconstrained delegation attack (Not counting the DC)! And there’s quite a bit more where that came from though that’s a wrap-up for this bit and moving onto the last section.

Notice the filter we used to locate AS-REPRoastable users and machines potentially vulnerable to unconstrained delegation. There are actually two properties in this filter: userAccountControl and 1.2.840.113556.1.4.803. We’re using the userAccountControl filter to search for objects with a certain property (full list of userAccountControl values and their meaning can be found here). That wierd number after is what’s called a matching rule (in this case the LDAP_MATCHING_RULE_BIT_AND, and is best explained in the LDAP filtering syntax section of the MS docs, which was previously linked in the LDAP filtering section) and allows us to search for bit positions as the userAccountControl property returns a bit flag unlike some other properties.

ACL Enumeration!

Before getting to actually enumerating and identifying potential permission misconfigurations on this network, maybe we should go over a quick refresher.

A Quick Refresher on AD Perms

As we are (or should be) aware, everything within an AD environment is considered an object from reg keys to user groups (OUs) and beyond. Each of these objects in a domain has what’s called an ACL (Access Control List). When we say ACL, it can be one of two things: DACLs or SACLs (As I’m not going to be going into much detail on these here, you can find more info here). Though in this case, we’ll be focusing on DACLs (this seems to be what many mean when they say “ACL” from most of my readings into the subject).

I mentioned there are two types of ACLs and the one we’ll be looking at will be DACLs (Discretionary Access Control Lists), as this is the one that actually handles object permissions. SACLs (Security Access Control Lists), on the other hand, are used generally for logging purposes.

Within these DACLs are what are called ACEs, or Access Control Entities, which are actually what tell us the permissions pertaining to an object. As with everything else, there are various types of permissions available. Let’s go over a few perms we, as attackers, may be interested in (a full list of AD perms can be found here):

  • GenericAll – Full access to the object (view/modify properties, add/remove to or from groups, create/delete children, etc. This perm gives the obejct full power over the object being affected)
  • GenericWrite – Allows an object to edit the properties of the affected object
  • WriteDACL – Allows an object to edit the DACL of affected object’s security descriptor
  • WriteOwner – Allows an object to take ownership of the affected object
  • ForcePasswordChange – Hence the name, allows an object to change the password of the affected object
  • Self – Allows an object to add themselves to a group

Now that we have a general idea of a few perms attackers would be looking for and what we’re going to be looking at, let’s see how we can enumerate object ACLs in the domain.

ACL Enumeration! (for real this time)

To enumerate ACLs for an object, we’re going to need to create an ADSI object pointing to the…object (no way around that one, deal with it) in question.

After creating an ADSI object pointing to the user Jonathan Taylor, we can view this user’s ACL with the ObjectSecurity.Access property. In this case, we are piping the output into select (alias to Select-Object) and returning only the first three objects (ACEs) in the list (recall the PS pipeline is based on objects instead of stdio like in *nix envs).

In the screenshot above, we can see three ACE’s pertaining to the j.taylor(queried user’s samaccountname) object. The properties we want to focus on from these ACEs include the following:

  • ActiveDirectoryRights – rights the ACE contains
  • AccessControlType – specifying if the ACE allows (Allow) or denies (Deny) the perms listed in ActiveDirectoryRights
  • IdentityReference – who has the rights specified in ActiveDirectortRights over the object being queried

Let’s break this down. We’ll hone in on the second ACE listed and see what information we can extrapolate from this.

We can see from the ActiveDirectoryRights property that this ACE is telling us something about the GenericAll permission (which allows full access to the object). Going down the list we also see that AccessControlType has a value of Allow, meaning whoever this ACE is referencing does have the GenericAll permission. Finally, right under we can see the IdentityReference property that tells us who this ACE is giving the permission to. Reading this ACE in its entirety, we can see that the NT AUTHORITY\SYSTEM account is able to exercise the GenericAll permission (hence the Allow) on the user Jonathan Taylor. I’ll leave the other two ACEs up to you, the reader, to read and figure out what permission(s) they hold over the queried object.

Now that we have a general idea of how to read this output, we can take a look and see if we can find any of these ACEs between users on this domain. As mentioned above, the IdentityReference property is what tells us who an ACE is giving permissions to. We can print the output of this by simply specifying the property.

Looks like the user object grimsec\m.seitz is listed, meaning an ACE on this object’s DACL is granting some permssion to the grimsec\m.seitz user over the current queried object.

Using a little PS magic, we can return only the ACE refencing the m.seitz domain user. Looking at the returned ACE, we can see that the m.seitz user has been granted GenericAll perms over j.taylor (the queried user’s samaccountname). Well that’s just a little scary. That means if we can gain access to m.seitz, we can do whatever we want with the j.taylor user. So let’s say j.taylor is a domain admin in this network, if we can take control of m.seitz, we could change j.taylor‘s password and become a domain admin that way. And just like that you have full control of the domain environment.

Quick side note about querying object DACLs: Sometimes when looking at the IdentityReference property, the ACEs may refer to objects as SIDs and not the format we see above ([domain]\[user]), I truly have no idea why windows does this. In that case, you could resolve the returned SIDs to user object names using the following line (this will fail within CLM as we aren’t allowed to call out to external libs).

((New-Object System.Security.Principal.SecurityIdentifier ($UserSid)).Translate([System.Security.Principal.NTAccount])).Value

Wrappin’ it all up

This is by no means an all-inclusive list on how to use the DirectoryServices namespace, merely an introduction (i.e its possible to use ADSI to create objects, add ACEs, etc.). I’ve scattered the resources I’ve used to dig up information on many of the ldap filters used across this article and linked them all below. If I were to list every single property and combinations, well it would never end. The documentation really is everywhere, but if you know where to look, that’s already half the battle won. The DirectoryServices is built into the AD environement by default and doesn’t require any external imports and doesn’t require much past just a bit of LDAP knowledge. For LDAP filtering, ldapwiki pretty much had it all.

While I have showcased all of this in PS, all I have done is leverage .NET namespaces. With all the protections and monitoring being implemented around PS as of late, it may be safer to use another language like C# which also makes use of the .NET environment without as many defenses for OPSEC purposes. Another thing to note is that a language like C# won’t be limited by something like CLM so some of the things we had to break CLM for would be able to work just fine.

Links n’ stuff!

Published inAdvanced TopicsTools, Techniques, and Grimmie?

Be First to Comment

Leave a Reply