Hunting the Delegation Access

Active Directory (AD) delegation is a fascinating subject, and we have previously discussed it in a blog post and later in a webinar. To summarize, Active Directory has a capability to delegate certain rights to non (domain/forest/enterprise) admin users to perform administrative tasks over a specific section of AD. This capability, if miss-configured, can become a major reason for AD compromise.

Earlier we only talked about manual analysis for finding such delegations. Another article which can be found here covered multiple other tools which can help in such manual analysis. Today, we are going to look at other possible options to hunt for these delegations across a network in an (semi-)automated manner via scripts.

Setting the scene

We’ll assume following scenarios:

  • We have previously compromised a low privilege domain user with severe restrictions such as powershell execution disabled via AppLocker.
  • We have a compromised local admin access on a domain joined machine.
  • This local admin access allows us to run unrestricted powershell scripts however we would require the domain login to perform enumeration on the AD domain.

To achieve that, we will use two different approaches:

  1. Using AD ACLScanner (Semi Automated) and
  2. Using Custom Powershell Script by NSS (Fully Automated)

Using ADACLScanner

This tool is written by canix1 and is useful for generic ACL scanning. It can be found on github ( We can repurpose this tool to perform the tasks of AD delegation hunting. We will explain this process with the help of an example below:

When you run a powershell script from ADACLScanner you are greeted with a nice GUI (one of the rare tools in powershell with a nice GUI).

ADACLScanner ADACLScanner

So let’s say, we connect to one of the AD named “plum”, available at as shown in the screenshot below.

Connecting to AD Connecting to AD

When we click on connect in the first column, we will be prompted to enter a domain credential so that it can enumerate the node. It should be noted that this domain credential could be of any low privilege user in the domain.

Requesting Domain Credentials Requesting Domain Credentials

Once we enter the domain credentials correctly, we will be shown the available nodes, as shown below.

Listing AD Nodes Listing AD Nodes

Now all we got to do is highlight the node in the first column, make sure inherited permissions is unticked and click on run scan. In the above scenario we selected the highest node that is “DC=plum,DC=local”. The report that is generated after the scan is completed, will look somewhat as shown below.

ACL Scanner Report ACL Scanner Report

If we highlight Regions node and run the scan then the report will look somewhat different. You can notice that the Object column in the report is giving you details of the node for which ACL report has been extracted. So the OU here is Regions.

ACL Report for Regions OU ACL Report for Regions OU

Similarly if you run scan for the USA OU from objects column as shown below, the report will state the delegation permissions for the OU of USA.

AD ACL Scanner report for OU USA AD ACL Scanner report for OU USA

The hassle here is that you have to manually hunt every node and then analyze every entry to find the correct delegation. It is fine for a small network but the task may become a nightmare if you are dealing with a large network. This is where our second approach could be useful.

Using Custom Powershell Script by NSS

Let me first show you the working of this script which has been prepared by our team


If you are only concerned about the automated script, here is the online version of it go and grab it. If you are interested in internal working of the script here is a block by block breakdown of the script.

  • Getting User Credentials and AD Drive Hack

We started with a non-domain, but local admin user. This is the reason that we get the below listed error whenever we try to mount an AD Drive or import active directory modules.

AD Module Import Error AD Module Import Error

To get around this, we passed “-WarningAction SilentlyContinue” parameter.

Let us dissect the script, the first bit reads like below:

Import-Module ActiveDirectory -WarningAction SilentlyContinue
# force use of specified credentials everywhere
$PSDefaultParameterValues = @{"*-AD*:Credential"=$creds}
# GET DC Name
New-PSDrive -Name AD -PSProvider ActiveDirectory -Server $dcname -Root //RootDSE/ -Credential $creds Set-Location AD:

Here is a better understanding of the command listed above:

Since we are performing actions as a non domain user, we started by importing “ActiveDirectory” module with “-WarningAction SilentlyContinue”. This allowed us to import the module but the AD Drive was not mounted. Next we attempted to get Credentials from the user. As user credentials were added we then set “PSDefaultParameterValues” for all Commands with “-AD” in them. Now we attempted to mount the AD Drive with this newly acquired credential and for this we needed a server name which we was seamlessly obtained using the “Get-ADDomainController” commandlet.

This would not be required if you are already logged in as a domain user. However we wanted to take the worst case scenario where you might have access to a system as a local admin hence unrestricted powershell access but limited domain user credentials.

  • Navigating Entire OU

Get all Domain Names, Organization Units, and individual ADObject

$OUs  = @(Get-ADDomain | Select-Object -ExpandProperty DistinguishedName)
$OUs += Get-ADOrganizationalUnit -Filter * | Select-Object -ExpandProperty DistinguishedName
$OUs += Get-ADObject -SearchBase (Get-ADDomain).DistinguishedName -SearchScope OneLevel -LDAPFilter '(objectClass=container)' | Select-Object -ExpandProperty DistinguishedName

Let us understand what happens here, the first line executes the “Get-ADDomain” and fetches the column of “DistinguishedName”, the second line adds to the OUs object content of “Get-ADOrganizationalUnit” starting filter is “*” and then taking the distinguished name from those objects. The third line fetches the AD objects of AD domain distinguished names, taking only one level with an “LdapFilter” where object class is container and printing out the “DistinguishedName” column.

  • Adding Exclusions
$domain = (Get-ADDomain).Name
$groups_to_ignore = ( "$domain\Enterprise Admins", "$domain\Domain Admins")

These lines show how we are adding more exclusions to the list. We are first fetching the domain name and post that,providing a list of groups to be ignored.

  • Extracting Relevant Domain User/Group Permissions
ForEach ($OU in $OUs) {
    $report += Get-Acl -Path "AD:\$OU" |
     Select-Object -ExpandProperty Access | ? {$_.IdentityReference -match "$domain*" -and $_.IdentityReference -notin $groups_to_ignore} |
     Select-Object @{name='organizationalUnit';expression={$OU}}, `
                   @{name='objectTypeName';expression={if ($_.objectType.ToString() -eq '00000000-0000-0000-0000-000000000000') {'All'} Else {$schemaIDGUID.Item($_.objectType)}}}, `
                   @{name='inheritedObjectTypeName';expression={$schemaIDGUID.Item($_.inheritedObjectType)}}, `

As we saw previously in second step (i.e. during navigation), we stored all the information in the $OUs, now here we are using a “ForEach” loop to extract all the information and process it.

The first three lines in the ForEach loop fetches the ACL path of all the entities in the $OUs by ensuring there is a match of “IdentityReference” with the Domain and not a part of the Groups to ignore list. The Groups to ignore list can be seen in step 4. Continuing from Line 4 the command basically selects objects like organizationalUnit with Expression of the entity in the $OUs and “ObjectTypeName” with condition that if the object type is equal to root GUID else fetch the details of the “SchemaIDGUID” based on the object type value.

  • Inheritance == False

Inheritance as false is the key to everything. We need only the lines where inheritance is false.

$filterrep= $report | Where-Object {-not $_.IsInherited}

This ensures that inherited objects are not shown in the output.

  • Array Conversion Array to Console Table
Write-Output ( $filterrep | Select-Object OrganizationalUnit,ObjectTypeName,ActiveDirectoryRights,IdentityReference | Format-Table | Out-String)

This finally results in a neatly formatted table with list of users having any non-inherited i.e. delegated rights on specific objects. By Default, the delegated rights cascade down the OU tree so if top level OU has the rights, it would automatically cascade down to the next OU section unless and until explicitly removed.

Result of Automated Script Result of Automated Script

shameless plug

This, and other such useful techniques, have been demonstrated in our latest Advanced Infrastructure Hacking course. We also provide in-house training and CTF's for internal security and SOC teams to help them advance their skill sets.