Free ways to simplify auditing AD

Rich
9 min readJul 30, 2022

TL;DR howto check for any DangerousRights held by a given user and/or audit for non-whitelisted users who have DangerousRights.

Welcome to Part XI 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

First off I cannot take credit for much of this. Both of these methods to query the entire domain or specific objects like OUs, groups, or users to find ‘DangerousRights’ were borrowed and then tweaked. I even borrowed the term “DangerousRights”. I am probably re-inventing the wheel, this stuff is probably out there somewhere already, but I didn’t feel like rummaging through github and I can always use practice and more learning.

I got the idea from Sean Metcalf, Tyler Robinson, and Darryl Baker on slide 46 here and then just started tweaking it to check things other than AD CS.

Please note that these auditing checks are just for privileges. There are also methods to bounce AD off ATCTS and automatically disable AD accounts that are non-compliant on ATCTS which have been incredibly useful in the past. I also created a function called Get-Audit that automated most of the items on our inspection checklist, such as spitting out a CSV with the details of stale user accounts.

Check for DangerousRights held by a given user

PowerView’s Invoke-ACLScanner already does this, however it trips Defender. I also wanted to be able to easily tweak the exact rights to check for, check the groups the user may be nested in, and learn something rather than just running PowerView.

Alex Hansen provided the function Get-ADUserNestedGroups here. We can take the output of that command, do some data parsing, store it in a variable, and then check the entire domain for any DangerousRights held by any of the groups in that variable. We will also check for any rights held directly by username or by everyone, just in case best practices were not followed at all.

The query is also on Mishky’s GitHub here.

#Run/import Get-ADNestedGroups.ps1 first! (Available from: http://blog.tofte-it.dk/powershell-get-all-nested-groups-for-a-user-in-active-directory/)Import-Module ActiveDirectory
Import-Module .\Get-ADNestedGroups.ps1
Set-Location AD:
$ADRoot = (Get-ADDomain).DistinguishedName
$Accounts = (Get-ADUserNestedGroups (Get-ADUser "$env:username" -Properties *).DistinguishedName).Name$MyGroups = $Accounts.ForEach{[regex]::Escape($_)} -join '|'
$MyGroups2 = $MyGroups.Replace('\','')
$AlsoCheck = "$env:username|Everyone|Authenticated Users|Domain Users"$ADCS_Objects = (Get-ADObject -Filter * -SearchBase $ADRoot).DistinguishedName$DangerousRights = "GenericAll|WriteDACL|WriteOwner|GenericWrite|WriteProperty|Self"$DangerousGUIDs = "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2|1131f6ad-9c07-11d1-f79f-00c04fc2dcd2|00000000-0000-0000-0000-000000000000|00299570-246d-11d0-a768-00aa006e0529"$FishyGUIDs = "ab721a56-1e2f-11d0-9819-00aa0040529b|ab721a54-1e2f-11d0-9819-00aa0040529b"ForEach ($object in $ADCS_Objects)
{
$BadACE = (Get-Acl $object -ErrorAction SilentlyContinue).Access | Where-Object {(($_.IdentityReference -match $MyGroups2) -or ($_.IdentityReference -match $AlsoCheck)) -and (($_.ActiveDirectoryRights -match $DangerousRights) -or ((($_.ActiveDirectoryRights -like "*ExtendedRight*") -and (($_.ObjectType -match $DangerousGUIDs) -or ($_.ObjectType -match $FishyGUIDs))))) -and ($_.AccessControlType -eq "Allow")}If ($BadACE)
{
Write-Host "Object: $object" -ForegroundColor Red
$BadACE
}
}

This pops hot on the couple of ACLs I modified just to make sure there was something to find.

PowerView has a resolve GUIDs option, which is one of its nicer features. I have been keeping a handy cheatsheet nearby (http://www.selfadsi.org/deep-inside/ad-security-descriptors.htm) with the GUIDs to name mapping. It may be needed to interpret some of the output of this query.

Giving it something to find

I simply used this to give the check something to find:

Import-Module ActiveDirectory
Set-Location AD:
$root = (Get-ADDomain).DistinguishedName
#Give a group GenericAll, aka Full Control, over a given user account
$victim = (Get-ADUser CEO -Properties *).DistinguishedName
$acl = Get-ACL $victim
$user = New-Object System.Security.Principal.SecurityIdentifier (Get-ADGroup -Identity “Minions”).SID
#Allow GenericAll
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $user,”GenericAll”,”ALLOW”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid,”None”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid))
#Apply above ACL rules
Set-ACL $victim $acl
#Give a group GenericAll, aka Full Control, over a given group
$victim = (Get-ADGroup Minions -Properties *).DistinguishedName
$acl = Get-ACL $victim
$user = New-Object System.Security.Principal.SecurityIdentifier (Get-ADGroup -Identity “Minions”).SID
#Allow GenericAll
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $user,”GenericAll”,”ALLOW”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid,”None”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid))
#Apply above ACL rules
Set-ACL $victim $acl

Quick tip; if you want to just as easily undo the changes after you ran the check, just hit Ctrl + H in PowerShell_ISE, replace AddAccessRule with RemoveAccessRule, and run it again.

Finding DangerousRights held by non-whitelisted groups or users

These samples will find DangerousRights held by non-whitelisted groups on OUs, groups, and users in the domain. I split them up due to nuances in specifically what to check for on each.

Check OUs:

$ADRoot = (Get-ADRootDSE).rootDomainNamingContext
$DomainSID = (Get-ADDomain).DomainSID.Value
#$ADCS_Objects = Get-ADObject -Filter * -SearchBase $ADRoot -SearchScope 2 -Properties *
$ADCS_Objects = (Get-ADOrganizationalUnit -Filter * -SearchBase $ADRoot).DistinguishedName
$Safe_Users = “Domain Admins|Enterprise Admins|BUILTIN\\Administrators|NT AUTHORITY\\SYSTEM|$env:userdomain\\CERT Publishers|$env:userdomain\\Administrator|BUILTIN\\Account Operators|$env:userdomain\\MSOL_06b14f1f684c|$DomainSID-519|S-1–5–32–548|NT AUTHORITY\\SELF|$env:userdomain\\Enterprise Key Admins|$env:userdomain\\Key Admins|BUILTIN\\Print Operators”
$DangerousRights = “GenericAll|WriteDACL|WriteOwner|GenericWrite|WriteProperty|DeleteTree|Delete|DeleteChild|CreateChild”
ForEach ($object in $ADCS_Objects)
{
$BadACE = (Get-Acl $object).Access | Where-Object {($_.IdentityReference -notmatch $Safe_Users) -and ($_.ActiveDirectoryRights -match $DangerousRights) -and ($_.AccessControlType -eq “Allow”)}
If ($BadACE)
{
Write-Host “Object: $object” -ForegroundColor Red
$BadACE
}
}
#Check the domain root as GPOs applied here are domain wide
$ReallyBadACE = (Get-Acl $ADRoot).Access | Where-Object {($_.IdentityReference -notmatch $Safe_Users) -and ($_.ActiveDirectoryRights -match $DangerousRights) -and ($_.AccessControlType -eq “Allow”)}
If ($ReallyBadACE)
{
Write-Host “Object: $ADRoot” -ForegroundColor Red
$ReallyBadACE
}

Check users:

$ADRoot = (Get-ADRootDSE).rootDomainNamingContext
$DomainSID = (Get-ADDomain).DomainSID.Value
$ADCS_Objects = (Get-ADUser -Filter * -SearchBase $ADRoot).DistinguishedName
$Safe_Users = “Domain Admins|Enterprise Admins|BUILTIN\\Administrators|NT AUTHORITY\\SYSTEM|$env:userdomain\\CERT Publishers|$env:userdomain\\Administrator|BUILTIN\\Account Operators|$env:userdomain\\MSOL_06b14f1f684c|$DomainSID-519|S-1–5–32–548”
ForEach ($object in $ADCS_Objects)
{
$BadACE = (Get-Acl $object).Access | Where-Object {($_.IdentityReference -notmatch $Safe_Users) -and ((($_.ActiveDirectoryRights -like “*ExtendedRight*”) -and (($_.ObjectType -eq “00299570–246d-11d0-a768–00aa006e0529”) -or ($_.ObjectType -eq “00000000–0000–0000–0000–000000000000”))) -or ($_.ActiveDirectoryRights -like “*GenericWrite*”) -or ($_.ActiveDirectoryRights -like “*GenericAll*”) -or ($_.ActiveDirectoryRights -like “*WriteOwner*”) -or ($_.ActiveDirectoryRights -like “*WriteDACL*”)) -and ($_.AccessControlType -eq “Allow”)}
If ($BadACE)
{
Write-Host “Object: $object” -ForegroundColor Red
$BadACE
}
}

Check groups:

$ADRoot = (Get-ADRootDSE).rootDomainNamingContext
$DomainSID = (Get-ADDomain).DomainSID.Value
$ADCS_Objects = (Get-ADGroup -Filter * -SearchBase $ADRoot).DistinguishedName
$Safe_Users = “Domain Admins|Enterprise Admins|BUILTIN\\Administrators|NT AUTHORITY\\SYSTEM|$env:userdomain\\CERT Publishers|$env:userdomain\\Administrator|BUILTIN\\Account Operators|$env:userdomain\\MSOL_06b14f1f684c|$DomainSID-519|S-1–5–32–548”
$DangerousRights = “GenericAll|WriteDACL|WriteOwner”
ForEach ($object in $ADCS_Objects)
{
$BadACE = (Get-Acl $object).Access | Where-Object {((($_.ActiveDirectoryRights -like “*WriteProperty*”) -and (($_.ObjectType -eq “bf9679c0–0de6–11d0-a285–00aa003049e2”) -or ($_.ObjectType -eq "bc0ac240-79a9-11d0-9020-00c04fc2d4cf") -or ($_.ObjectType -eq “00000000–0000–0000–0000–000000000000”))) -or ($_.ActiveDirectoryRights -like “*GenericWrite*”) -or ($_.ActiveDirectoryRights -like “*GenericAll*”) -or ($_.ActiveDirectoryRights -like “*WriteDACL*”) -or ($_.ActiveDirectoryRights -like “*WriteOwner*”) -or (($_.ActiveDirectoryRights -like “*Self*”) -and (($_.ObjectType -eq “bf9679c0–0de6–11d0-a285–00aa003049e2”) -or ($_.ObjectType -eq “00000000–0000–0000–0000–000000000000”))) -and ($_.AccessControlType -eq “Allow”) -and ($_.IdentityReference -notmatch $Safe_Users))}
If ($BadACE)
{
Write-Host “Object: $object” -ForegroundColor Red
$BadACE
}
}

Alternatively one can also check OUs against a white list using Mishky’s Blue Team Auditor.

Mitigating privileges that shouldn’t be there

Of course if we realized that any given group really should not have a privilege listed then we can simply remove the offending ACE (using the Minions flagged above):

Import-Module ActiveDirectory
Set-Location AD:
$root = (Get-ADDomain).DistinguishedName
$victim = (Get-ADGroup “Minions” -Properties *).DistinguishedName
$acl = Get-ACL $victim
$user = New-Object System.Security.Principal.SecurityIdentifier (Get-ADGroup -Identity “Minions”).SID
$acl.RemoveAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $user,”GenericAll”,”ALLOW”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid,”None”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid))
#Apply above ACL rules
Set-ACL $victim $acl

Alternatively if it turns out that the rights are inherited down from a higher OU then simply remove them from there. However let’s say it’s more than just one ACE, like the issue I ran into with the old AD account my prior Azure AD Connect installation had created before the server crashed. Simply use the below:

Import-Module ActiveDirectory
Set-Location AD:
#https://ex-shell.com/2017/06/16/remove-a-usergroup-permission-on-an-ad-object-via-powershell/$DistinguishedName = (Get-ADDomain).DistinguishedName
#$user = “domainjdoe” (to use this substitute $user for $Stale_SID on line 15)
$Stale_SID = “S-1–5–21–4103247791–2828088783–3009141321–3631”
#Collect the current ACL
$Acl = Get-Acl $DistinguishedName
#Loop each access permission in the ACL
foreach ($access in $acl.Access)
{
if ($access.IdentityReference.Value -eq $Stale_SID)
{
$acl.RemoveAccessRule($access)
}
}
#Set the ACL Back to the AD Object
set-acl $DistinguishedName -AclObject $acl

This will remove all privileges that were granted to that group or user. I had to use the SID because I had already deleted the account. Please note that AD does not automatically remove an account from ACLs just because you deleted it. Also, a very big thank you to Steve Man at ex-shell.com for making that simple!

Also make sure you check who has privileges to DCSync, described here. I did not include that in the checks listed above due to nuances in the query.

Please note that these also do not check for sneaky persistence such as this or this. If you are truly paranoid then run those checks as well.

Of course Trimarc’s idea can be tweaked to check for rogue owners as well, for example a quick & dirty query:

Import-Module ActiveDirectory
Set-Location AD:
$ADCS_Objects = (Get-ADObject -Filter * -SearchBase $ADRoot).DistinguishedName
$Safe_Users = “Domain Admins|BUILTIN\\Administrators|NT AUTHORITY\\SYSTEM”
ForEach ($object in $ADCS_Objects)
{
$BadOwner = (Get-Acl $object -ErrorAction SilentlyContinue).Owner -notmatch $Safe_Users
If ($BadOwner)
{
Write-Host “Object: $object” -ForegroundColor Red
(Get-Acl $object -ErrorAction SilentlyContinue).owner
}
}

Summary

I originally started taking notes because my memory sucks. I started blogging because doing so forces me to take better notes. If anyone stumbles on this from Google and it helps you then I am flattered. However the primary purpose of everything here is still for myself really.

It occurs to me that I really need to put together a cheatsheet of commands, queries, etc. I have been going through all these projects over the last year or two and I find myself going back to something I did a year ago to copy/paste and then tweak something all the time. Well that’s a project for another time.

I also need to put a pin in this whole AD thing and get back to learning AAD. It turns out that to keep a Premium P2 license for one user, in this case my kid’s Global Admin account, runs $9 a month. While test.local has gone to The Cloud it is no longer free.

AD just happens to be what keeps me up late at night recently. Also, you won’t hear any of this from Microsoft. You may hear it from Pentester Academy or TCM, or my favorite source CW6 Google. Hence it happens to interest me, and if I am going to home lab with a refurbished laptop, a refurbished HP server, and a $9 a month AAD subscription then I am going to focus on stuff I care about at that moment in time.

References

Get all nested groups for a user: http://blog.tofte-it.dk/powershell-get-all-nested-groups-for-a-user-in-active-directory/

Handy GUID cheatsheet: http://www.selfadsi.org/deep-inside/ad-security-descriptors.htm

GUIDs matched to names: https://www.powershellgallery.com/packages/DSACL/1.0.0/Content/DSACL.psm1

Trimarc’s Top 10 Ways to Improve AD Security Quickly: https://www.hub.trimarcsecurity.com/post/webcast-top-10-ways-to-improve-active-directory-security-quickly

Remove all entries for a given entity on an ACL: https://ex-shell.com/2017/06/16/remove-a-usergroup-permission-on-an-ad-object-via-powershell/

--

--

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.