Discover Hidden GPO(s) on Active Directory using PS>ADSI

Tasos Chatziefstratiou
6 min readApr 25, 2020

--

In the summer of 2019 I came across a write-up from @huykha10 named “gpo abuse you can’t see me”. I admit that the title was very attractive and it triggered me to set up a hidden GPO on my virtual Active Directory environment and study about that.I have made my research and I learnt a ton of tricks that boosted positively my confidence.The motivated write-up does not exist anymore but I will do my best to abstract the offensive side of huykha’s post.

In my quest to result in effective PowerShell script, I encountered a few posts about Active Directory enumeration using ADSI and the majority has a preference in tools that some other individuals or teams have implemented. The latter has pros and cons as you know. From a developing perspective I wanted to create a functional and easy to use script that wouldn’t need any additional component or administrative rights to work. From a security perspective I would like to create something that it could be used from Offensive, Defensive and Audit teams as well.

Purpose

Primary scope of this post is to discover and remove this potential threat from an Active Directory environment as well as share my journey and help people who want to utilize the untapped ADSI. Identification of GPO(s) that might be hidden even from users with administrative rights is an easy job but the steps that I followed in my demonstration may discourage you to use PowerShell and especially if you are not familiar with the latter. On that point some questions may emerge about the implications of a hidden GPO in an Active Directory environment and on how that is possible!

Users that are not familiar with an Offensive mindset may confront some difficulties identifying the impact of this threat. On the other hand, some Offensive teams will never present/implement this type of attack as a threat-in-scenario if they are not familiar with a defensive mindset.

Earlier, I mentioned the word ADSI and for those that aren’t comfortable with the usage and the syntax of this great component, I strongly recommend to take a quick look here. From an offensive perspective ADSI is the dominant enumeration method from pentesters and threat agents for various reasons.

“You don’t need to have administrative rights.”

Before we get deeper into PowerShell and ADSI in order to reach for the intended result lets break the huykha’s title down.

  1. GPO: Group Policy provides centralized management and configuration of operating systems, applications, and users’ settings in an Active Directory environment.
  2. You can’t see me (Hidden): What constitute a GPO to hidden even from a privileged domain user or group (e.g Domain Administrator)?

Answer: Deny to read from “Everyone”.

figure 1 — Setup a hidden GPO

After accepting the changes backdoorGPO changes its status to inaccessible.

Outcome — Offensive Side

The following screenshots illustrate the outcome of the PowerShell script which you can either grab from my GitHub repository or copy/paste directly from this post. Firstly we must understand where we are sitting on. I already undertook to reach to the intended result using only PowerShell ADSI.

PS> [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

Figure 2 - Identify Domain Name

I would like to highlight the implications of my clumsy coding that produced false positives, which is my worst enemy during the creation of my coding projects. Observe the output of the following code on figure 3 and compare it with next inline script (figure 4).

PS> (([adsisearcher]’(objectcategory=organizationalunit)’).FindAll()).Path | %{if(([ADSI]”$_”).gPlink){Write-Host “[+] OU Path:”([ADSI]”$_”).Path;$a=((([ADSI]”$_”).gplink) -replace “[[;]” -split “]”);for($i=0;$i -lt $a.length;$i++){if($a[$i]){Write-Host “Policy Path[$i]:”([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).Path;Write-Host “Policy Name[$i]:”([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).DisplayName} };Write-Output “`n” }}

figure 3 —

PS>(([adsi]’LDAP://DC=adsecurity,DC=lab’),(([adsisearcher]’(objectcategory=organizationalunit)’)).findall()).Path | %{if(([ADSI]”$_”).gPlink){Write-Host “[+] OU Path:”([ADSI]”$_”).Path;$a=((([ADSI]”$_”).gplink) -replace “[[;]” -split “]”);for($i=0;$i -lt $a.length;$i++){if($a[$i]){Write-Host “Policy Path[$i]:”([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).Path;Write-Host “Policy Name[$i]:”([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).DisplayName} };Write-Output “`n” }}

Figure 4 — Linked Policies in OUs

In case that we are interested to seek the linked policies applied only to the ROOT Domain then we should use the following code:

PS>(([adsisearcher]’’).SearchRooT).Path | %{if(([ADSI]”$_”).gPlink){Write-Host “[+] Domain Path:”([ADSI]”$_”).Path;$a=((([ADSI]”$_”).gplink) -replace “[[;]” -split “]”);for($i=0;$i -lt $a.length;$i++){if($a[$i]){Write-Host “Policy Path[$i]:”([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).Path;Write-Host “Policy Name[$i]:”([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).DisplayName} };Write-Output “`n” }}

figure 5 — Root Domain linked policies

Reaching the end of our developing for a more effective script I would like to highlight something that I noticed during the creating process. The script above ended with a NULL name and path after crossing the hidden GPO. Observe the results of the script on the figure 6.

figure 6 — Null name of a hidden GPO

In order for the results to look more elegant I had to modify the script again with additional output information. See figure 7

PS>(([adsi]’LDAP://DC=adsecurity,DC=lab’),(([adsisearcher]’(objectcategory=organizationalunit)’)).findall()).Path | %{if(([ADSI]”$_”).gPlink){Write-Host “[+] OU Path:”([ADSI]”$_”).Path;$a=((([ADSI]”$_”).gplink) -replace “[[;]” -split “]”);for($i=0;$i -lt $a.length;$i++){if($a[$i]){Write-Host “Policy Path[$i]:”($a[$i]).Substring(0,$a[$i].length-1);$b=([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).DisplayName;if($b){Write-Host “Policy Name[$i]:”$b}else{Write-Host “Policy Name[$i]: HIDDEN POLICY!!!”}}};Write-Output “`n”}}

figure 7 — Hidden Policy

Lately, I’ve evaluated the script and I noticed that some useful information was missing. From the previous outputs are you able to identify the missing puzzle?!

Answer: Linked, Enforced and Inheritance!

PS>$domain=([ADSI]”LDAP://RootDSE”).rootDomainNamingContext;(([adsi]”LDAP://$domain”),(([adsisearcher]’(objectcategory=organizationalunit)’)).findall()).Path | %{if(([ADSI]”$_”).gPlink){Write-Host “[+] OU Path:”([ADSI]”$_”).Path;$a=((([ADSI]”$_”).gplink) -replace “[[;]” -split “]”);for($i=$a.length;$i -ge 0;$i — ){if($a[$i]){Write-Host “Policy Path[$i]:”($a[$i]).Substring(0,$a[$i].length-1);$status=$a[$i].Substring($a[$i].length-1,1);$b=([ADSI]($a[$i]).Substring(0,$a[$i].length-1)).DisplayName;if($b){Write-Host “Policy Name[$i]:”$b}else{Write-Host “Policy Name[$i]: HIDDEN POLICY!!!”} switch($status) {“0” {“Linked[$i]: True”;Write-Host “Enforced[$i]: False”} “1” {“Linked[$i]: False”;Write-Host “Enforced[$i]: False”} “2” {“Linked[$i]: True”;Write-Host “Enforced[$i]: True”} “3” {“Linked[$i]: False”;Write-Host “Enforced[$i]: True”} };if(([ADSI]”$_”).gPOptions){Write-Host “BlockInheritance[$i]: True”}else{“BlockInheritance[$i]: False”} Write-Output “`n” }};Write-Output “`n”}}

Threat removal — Blue Side

Earlier I presented to you a way to identify the hidden GPO on your Active Directory environment using PowerShell and ADSI. Now, it’s time to remove the potential threat from our environment using the Group Policy Management editor but before doing that I would like to draw the impact of this backdoor. Audit teams should also pay attention to the following figure.

figure 1 — Generating report

Although my user is a member of a privileged group, I don’t have the permissions to generate report for backdoorGPO. Audit teams will fail to uncover this type of GPO in their assessments.

Now that we have an insight of what constitutes a hidden GPO, let’s remove it from our environment. Firstly, identify the cn (Common Name)/UniqueID using either the PowerShell script (figure 2) or Group Policy Management editor (figure 3).

figure 2 — backdoorGPO / CN (Powershell)
figure 3 — backdoorGPO / CN (Group Policy Management editor)

Those who already tried to delete the hidden GPO from the Group Policy Management editor propably faced the below (figure 4) annoying information message.

figure 4

To successfully remove the backdoorGPO you should launch Server Manager and shift towards Active Directory Users and Computers > System > Policies. It is evident that the cn/UniqueID name of the backdoorGPO is included among other GPOs. To remove it, you have to edit its permissions otherwise another error message will prompt.

figure 5
figure 6— Edit permissions

We setup everything right in order to remove the hidden GPO, last step is lauch Group Policy Management editor, identifying the backdoorGPO and delete it.

figure 7 — Delete hidden GPO

Conclusion

If you are a member of a Blue Team or Audit team I would highly recommend to start learning Powershell because Powershell has superior insight about Windows and it can facilitate your every day routines. In addition, it will help you to develop your skills and your mindset.

Thanks for reading! Grab the latest Powershell script from here.

References

  1. http://www.jaapbrasser.com/wp-content/uploads/2012/11/Adsisearcher-Examples.txt
  2. https://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WIL~Extenders/ADSI+Find~Exchange~Servers.txt

--

--