Group Policy Preferences (GPP) has been around for a considerable amount of time, yet I still keep finding remmants of GPP passwords lying around within an Active Directory (AD) forests. For more background on GPP passwords you can read up on it here on Active Directory Security.

I was tasked with an AD review and was met with a forest with a good 200+ Group Policy Objects (GPO). No idea why they had that insane amount. I got out my favourite script included in PowerSploit and began to run it. A few hours later it concluded and did give me a great result, however, I felt this could be sped up somehow.

Understanding Get-GPPPassword.ps1

Looking at the script we can see on line 234 we have the following:

$XMlFiles = Get-ChildItem -Path "\\$Server\SYSVOL" -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml'

This will recursively search every file within SYSVOL for an XML as defined within the Include paramerter. Now if you have 200+ GPOs and are looking in every folder for one of 6 files this soon mounts up to a long time to wait. Not forgetting those sys admins who have created a Central Group Policy Store giving additional files to crawl through.

I see nothing wrong with this for small well managed forests, but will generate a lot of traffic over an extended period of time to the SYSVOL share. This possibly generating unwanted attention if you were conducting a red team for example.

Client Side Extensions (CSE)

I set to research on how I could identify a GPO before making a connection to the SYSVOL. I brought up an AD object for the GPO and inspected the properties and one attribute caught my attention, namely gPCMachineExtensionNames. So I set to investigate which extensions are used by GPP that could contain passwords, I found a handy TechNet post by Mark Empson that gave me the following GUIDs that related to the following files:

  • {5794DAFD-BE60-433f-88A2-1A31939AC01F} - Drives.xml
  • {17D89FEC-5C44-4972-B12D-241CAEF74509} - Groups.xml
  • {B087BE9D-ED37-454f-AF9C-04291E351182} - Registry.xml
  • {CAB54552-DEEA-4691-817E-ED4A4D1AFC72} - Scheduledtasks.xml
  • {728EE579-943C-4519-9EF7-AB56765798ED} - DataSources.xml
  • {91FBB303-0CD5-4055-BF42-E512A681B325} - Services.xml

We can now quickly enumerate in a single LDAP query all the GPOs that will have a GPP. This reducing down all those SYSVOL connections to a single query. Using PowerShell as a quick test I identified that this worked well, below is an example command returning a GPO that would contain a Groups.xml file:

> ([adsisearcher]"(gPCMachineExtensionNames=*{17D89FEC-5C44-4972-B12D-241CAEF74509}*)").findall() | foreach{ $_.Properties.gpcfilesyspath[0] }

This will greatly reduce the time it takes to search for a Groups.xml file as per the original approach.

More Enhancements

I wanted to also reduce the amount of effort you need to then identify all the affected Organizational Units (OUs) and within those the active computers that could be viable targets to attempt the password against.

So using the list of identified GPO GUIDs (The unique ID of the GPO) I could then use the gPLink attribute on the OU object to enumerate which OUs had this policy linked. With this list I could then enumerate all subsequent sub OUs below. For example, using the LDAP query (&(objectclass=organizationalUnit)(!(gPOptions=1))) will return all OUs that the policy can reach while taking into account the blocked inheritance OUs. Blocked inheritance will stop a GPOs configuration applying to objects contained within or below it, however, some GPO links can be enforced in that instance I simply omitted the gPOptions portion of the query.

With an OU list to hand it was a simple case of passing this to my existing script and enumerating the active computer objects. Giving us a nice list of machines to work with.

The End Result

Once this had all been built I compared the results of the two scripts against an AD forest that contained around 80 GPOs, they also had a lot of junk stored in SYSVOL that was not relevant to GPP. Additionally, I was working over a VPN so the amount of time taken could have been increased as a result.

However, lets measure the timing of the old versus the new approach:

PS C:\> Measure-Command { Get-GPPPassword }
Days              : 0
Hours             : 0
Minutes           : 10
Seconds           : 21
Milliseconds      : 104
Ticks             : 6211048780
TotalDays         : 0.00718871386574074
TotalHours        : 0.172529132777778
TotalMinutes      : 10.3517479666667
TotalSeconds      : 621.104878
TotalMilliseconds : 621104.878
PS C:\> Measure-Command { Invoke-GPPCSE }
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 10
Milliseconds      : 480
Ticks             : 104805931
TotalDays         : 0.00012130316087963
TotalHours        : 0.00291127586111111
TotalMinutes      : 0.174676551666667
TotalSeconds      : 10.4805931
TotalMilliseconds : 10480.5931

As you can see a vast reduction in time required to enumerate and return the results. Very useful if you need to act quickly!

You can download the script from my GitHub Repository.