Do you know who has local admin in your domain?

Rich
6 min readJun 3, 2023

--

TL;DR how to scrub Group Policy for domain groups being put in local admins on workstations.

Welcome to the annexes of our Auditing AD Series!

Part I: 3 warm up questions to get us started

Part II: Who can reset the CISO’s password?

Part III: Who can execute DCSync?

Part IV: Who can modify the Domain Admins group?

Part V: Domain dominance in minutes via ACL abuse (i.e. why auditing AD matters)

Part VI: Why Allow statements matter a LOT more than Deny ones

Part VII: Sneaky persistence via hidden objects in AD

Part VIII: SDDL, what is it, does it matter?

Part IX: Do you know who owns your domain?

Part X: Who can push ransomware via Group Policy?

Part XI: Free ways to simplify auditing AD

Part XII: Sidenote on arcane rights like Self

Part XIII: Sidenote on the ScriptPath right

Part XIV: Self & so called “Effective Permissions”

Part XV: Inheritance Explained & an Example

Part XVI: Summary of our Auditing AD Series

Annex A: Scrubbing Group Policy for local admins

Annex B: What Property Sets in AD are, & why they matter

Annex C: Dangerous Rights & RE GUIDs Cheatsheet

Annex D: Mishky’s Blue Team Auditor

Annex E: Even ChatGPT gets this stuff wrong

Annex F: Get-ADPermission Cheatsheet

Annex G: Mishky’s Red Team Enumerate & Attack tools

Background

Like many ideas we have tried in the home lab, this one was prompted by a question. Someone was asking how to find what AD groups have been put in the local administrators group on workstations via a GPO.

It’s a good question. We have covered auditing who can create, modify, or link GPOs to an OU, but not what already existed. This is important to consider as a prior system administrator might have gotten sloppy, for lack of a better word. Often there is a “just make it work” mindset, and security is not always contemplated.

In an ideal world there should be documentation, but we all know it is not always ideal. So how would one go about finding what pre-existing GPOs are putting AD groups in local admins?

Restricted Groups in Group Policy

This part of Group Policy is rather oddly and counterintuitively named. As I am sure most readers already know, it puts the specified AD group in the specified local group. The path is

Computer Configuration\Policies\Windows Settings\Security Settings\Restricted Groups

Finding if this is set in a GPO somewhere

My initial thought was to simply check the local admins group on the workstations.

$Computers = (Get-ADComputer -Filter * -SearchBase "ou=clients,dc=test,dc=local").Name
ForEach($Computer in $Computers)
{
Invoke-Command -ComputerName $Computer -ScriptBlock {Get-LocalGroupMember -Group "Administrators" | Export-Csv .\LocalAdmins.csv}
}

However this will only show you what groups were added, not what GPO was responsible for it. It also relies on the workstations being powered on and online, not to mention it would be slow to check if there are very many endpoints.

However, there is another way to go about this. What if we checked the SYSVOL directly? After all, GPO configurations are stored there in folders by their GUID.

The thought occurred to me that one could simply ‘grep’ the SYSVOL for the SID of the local admins group and then parse the resulting data to identify the GPO in question, what AD group it applies to, and so on.

One can get the SID for the local groups via:

Get-LocalGroup | Select-Object SID, Name, Description
Please note I added “ | Where-Object {$_.SID -notlike “S-1–5–21”} “ IOT omit groups added by VMware, OpenVPN, etc.

I have not put any domain groups in the local admins on workstations in the lab, quite the opposite actually. We have demonstrated how to setup things so that the helpdesk can manage the workstations using LAPS and how to delegate them privileges on the OU.

Hence we used ‘Remote Desktop Users’ with SID S-1–5–32–555 in our test case. We use this group in the lab since most of our VMs are in ESXi, including domain workstations used for testing.

The query

This is not the prettiest code, however it will dump the GPO DisplayName and AD group to a text file for any GPOs that put an AD group in the specified local group.

If you are checking local admins then just put $LocalGroup = “S-1–5–32–544”.

#'grep' SYSVOL & find the GPO that puts an AD group in a local group
$ErrorActionPreference = 'SilentlyContinue'
$LocalGroup = "S-1–5–32–555"
$DomainFQDN = (Get-ADDomain).DNSRoot
$GPOfiles = (Get-ChildItem \\$DomainFQDN\SYSVOL\$DomainFQDN\Policies\ -Recurse | Select-String "$LocalGroup" -List | Select Path).Path

ForEach($GPOfile in $GPOfiles)
{
$GPOGUID = ($GPOfile.Split("{")[1]).Split("}")[0]
$RawContent = Get-Content $GPOfile | Select-String -Pattern "$LocalGroup"

#Parse the data & get the AD group's SID from the raw data
$Temp = (($RawContent -split("=") | ConvertFrom-String).P2).Replace('*','')
$string = ($Temp | Out-String) ; $GroupSID = $string -replace '\s',''

$ADGroup = (Get-ADGroup -Filter * -Properties * | Where-Object {$_.SID -like "*$GroupSID*"}).CN
Add-Content -Path C:\Users\Public\Documents\GPOs.txt "This is the AD group that's added to the specifid local group via GPO:"
$ADGroup | Out-File C:\Users\Public\Documents\GPOs.txt -Append
Add-Content -Path C:\Users\Public\Documents\GPOs.txt " "

#Find what OU the GPO is applied to
$OUAppliedTo = (Get-ADOrganizationalUnit -Filter * -Properties * | Where-Object {$_.gPLink -like "*$GPOGUID*"}).Name
Add-Content -Path C:\Users\Public\Documents\GPOs.txt "This is the OU that the GPO is applied to:"
$OUAppliedTo | Out-File C:\Users\Public\Documents\GPOs.txt -Append
Add-Content -Path C:\Users\Public\Documents\GPOs.txt " "

#Show the GPO that is applying this group
Add-Content -Path C:\Users\Public\Documents\GPOs.txt "This is the GPO's Display Name that is adding the group above to the specified local group:"
$root = (Get-ADDomain).DistinguishedName ; (Get-ADObject -Filter * -SearchBase "cn=policies,cn=system,$root" -Properties * | Where-Object {$_.Name -like "*$GPOGUID*"}).DisplayName | Out-File C:\Users\Public\Documents\GPOs.txt -Append

Add-Content -Path C:\Users\Public\Documents\GPOs.txt " "
Add-Content -Path C:\Users\Public\Documents\GPOs.txt " - - Next GPO - - "
Add-Content -Path C:\Users\Public\Documents\GPOs.txt " "
}

One can then simply query any additional information desired, such as all members of the AD group:

(Get-ADGroupMember -Identity “RDP Users” -Recursive).SamAccountName

Or grab all the information related to the GPO:

$root = (Get-ADDomain).DistinguishedName ; Get-ADObject -Filter * -SearchBase “cn=policies,cn=system,$root” -Properties * | Where-Object {$_.DisplayName -eq “Mishky’s RDP Policy for users”}

Or all the computers in the OU that the GPO is applied to:

$DN = (Get-ADOrganizationalUnit -Filter {Name -like “*Clients*”}).DistinguishedName ; (Get-ADComputer -Filter * -SearchBase $DN).SamAccountName

Bear in mind that in a sloppy domain environment there could easily be more than one GPO that is putting an AD group in a local group, hence why we ran a ForEach loop.

Summary

We ran a 15 part series on auditing privileges in AD, aka checking delegation of privileges. We have also covered things like finding stale users, checking for non-compliant users, and of course checking for things like ASREProastable accounts, Kerberoastable accounts, Delegation being enabled on endpoints, and so on.

However we had not considered the case of a prior system administrator putting AD groups in local groups previously. This is an important consideration if one is taking over or auditing a poorly documented environment, so it was an excellent question.

We learned a few things while finding the answer to it. If at nothing else it was a good exercise in parsing data. I figured I’d post my notes in case it helps anyone else, or I need to do this again in the future. That is after all why any of these howtos are on here in the first place.

References

Group Policy Restricted Groups: https://learn.microsoft.com/en-us/troubleshoot/windows-server/group-policy/description-of-group-policy-restricted-groups

Select-String: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-string?view=powershell-7.3

Remove whitespace: https://stackoverflow.com/questions/24355760/removing-spaces-from-a-variable-input-using-powershell-4-0

ConvertFrom-String: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertfrom-string?view=powershell-5.1

Out-String: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-string?view=powershell-7.3

--

--

Rich

I work various IT jobs & like Windows domain security as a hobby. Most of what’s here is my notes from auditing or the lab.