Who Can Push Ransomware Domain Wide?

Rich
9 min readJul 30, 2022

TL;DR how to audit OUs and Group Policy in order to find out who can create accounts, delete stuff, link a GPO to an OU, or modify existing GPOs.

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

One cannot really talk about Group Policy or Organizational Units (OUs) in AD without talking about the other. Group Policy is applied to OUs after all and the permissions on a given OU dictate who can link a GPO to that OU. Group Policy and OUs are not just system administration stuff for only sysadmins to worry about as attackers have realized that Group Policy works for pushing malware. LockBit 2.0 for example uses Group Policy to automatically propagate. Therefore auditors and security folks should also be familiar with the topic.

Quick refresher on Group Policy

Group Policy is made up of Group Policy Objects (GPOs). These GPOs are located in the Policies container inside the System container in AD.

The path is ‘cn=Policies,cn=System,dc=test,dc=local’. You can list all of them showing the name and GUID by

$root = (Get-ADDomain).DistinguishedName ; Get-ADObject -Filter * -SearchBase “cn=policies,cn=system,$root” -Properties * | Select-Object DisplayName, Name

This query is quite handy to match the GPOs’ GUIDs to the easily understood names that you see in the Group Policy Management Console.

The configurations themselves that are pushed by these GPOs are stored in the SYSVOL, located at

\\<domain FQDN>\SYSVOL\<domain FQDN>\Policies

We went over this a bit and showed how to update Group Policy by using the central store here.

You can also check delegation in GPMC, but I am not sure I’d rely on that alone.

I’ll explain below why that is.

Security considerations

By default Administrators and Group Policy Creator Owners can write to the folder containing Group Policy configurations in the SYSVOL. This is set in the NTFS permissions.

The share permissions are set to give Authenticated Users Full Control, so bear that in mind.

(Get-Acl “\\test.local\SYSVOL\test.local”).Access | Where-Object {($_.FileSystemRights -match “TakeOwnership|ChangePermissions|FullControl|Write”)}Get-SmbShareAccess -Name “SYSVOL”

Additionally, always keep in mind that the Creator/Owner of a GPO has Full Control. As Sean Metcalf has pointed out, this continues to apply regardless of where that GPO is applied. For example let’s say a lowly helpdesk group is allowed to create GPOs and link them to an OU containing some non-critical domain workstations. If a Domain Admin links one of those GPOs to the OU containing member servers, like the Azure AD Connect server, then the helpdesk will effectively be given control of those servers.

Also bear in mind that everyone has read access to SYSVOL by default, and of course everyone can easily enumerate what Group Policy is applied simply by running RSOP.msc. Therefore don’t get careless and put sensitive info in logon scripts, for example.

Auditing who can push Group Policy

So who could push ransomware by linking a GPO? The short answer is anyone who has the gpLink privilege on an OU. Of course as everyone knows there are numerous other privileges which either include that or grant someone the ability to give themselves the privileges. Therefore one would query for the following ‘Dangerous Rights’ (Sean Metcalf’s term):

  • GenericAll (grants all privileges)
  • WriteDACL (grants one the right to give oneself privileges)
  • WriteOwner (grants the right to seize ownership, and then give oneself privileges)
  • GenericWrite (Functionally the same thing as WriteProperty with ObjectType all 0s)
  • WriteProperty (with ObjectType = f30e3bbe-9ff0–11d1-b603–0000f80367c, or all 0s, or just any WriteProperty)

While you’re at it, audit these too:

  • Delete (self-explanatory)
  • DeleteChild (delete the items in the OU)
  • CreateChild (create items, aka users, in the OU)
  • DeleteTree (self-explanatory)

One can query specifically for WriteProperty with ObjectType all 0s, or the specific GUID that matches gpLink, but unless your organization has a legitimate business need to delegate the privilege to change the State, or StreetAddress, or some other random non-security related property on OUs to those who should NOT be able to touch Group Policy it doesn’t make much sense.

These are all the properties on the OU in test.local that contains domain workstations. As one can see, just audit who can WriteProperty at all, on any properties.

Hence in order to query a single OU one can simply:

Import-Module ActiveDirectory
Set-Location AD:
$object = “ou=clients,dc=test,dc=local”
$DangerousRights = “GenericAll|WriteDACL|WriteOwner|GenericWrite|WriteProperty|DeleteTree|Delete|DeleteChild|CreateChild”
Get-Acl $object).Access | Where-Object {($_.ActiveDirectoryRights -match $DangerousRights) -and ($_.AccessControlType -eq “Allow”)}

However this will show a bunch of entries that give rights to builtin AD groups like Administrators, Domain Admins, Enterprise Admins, etc. But what if we could whitelist groups that should have privileges, check all the OUs, and flag groups that aren’t whitelisted and hold these DangerousRights?

Taking this one step further

I cannot take credit for this. I borrowed the idea from Sean Metcalf, Tyler Robinson, and Darryl Baker’s idea on slide 46 here and tweaked it to check OUs for non-whitelisted groups who have privileges.

#Borrowed from Sean Metcalf, works :)
#Basically it scans the ACL of every OU & flags anyone who has DangerousRights and is not a Safe_User
#Most of the variable names are Sean Metcalf’s, I just tweaked it to check OUs instead of AD CS
$ADRoot = (Get-ADRootDSE).rootDomainNamingContext
$DomainSID = (Get-ADDomain).DomainSID.Value
$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_xyz|$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
}
}
$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
}

Awhile back in prior labs here and here we delegated privileges to the helpdesk. Since I didn’t whitelist them they popped hot.

We expect to see that, as outlined in the LAPS lab last year we delegated the helpdesk the ability to pull LAPS passwords, among other things on the Staging and Clients OUs.

Just to make sure this check is functioning properly, we also gave a group of mere Domain Users GenericAll on the VIP OU:

Import-Module ActiveDirectory
Set-Location AD:
$root = (Get-ADDomain).DistinguishedName
#Give a group GenericAll, aka Full Control, over a given OU
$victim = (Get-ADOrganizationalUnit “ou=vips,$root” -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

Sure enough, Sean’s idea shows us the ACE and the OU it is applied to that is granting DangerousRights to a non-whitelisted entity.

Mitigating privileges that shouldn’t be there

Of course if we realized that the group Minions really should not have this privilege then we can simply remove the offending ACE:

Import-Module ActiveDirectory
Set-Location AD:
$root = (Get-ADDomain).DistinguishedName
$victim = (Get-ADOrganizationalUnit “ou=vips,$root” -Properties *).DistinguishedName
$acl = Get-ACL $victim
$user = New-Object System.Security.Principal.SecurityIdentifier (Get-ADGroup -Identity “Minions”).SID
#Allow GenericAll
$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!

Quick sidenote on containers

One can query the privileges on the builtin containers such as Users and Computers the same as we queried a single OU earlier. Just remember to substitute ‘cn’ for ‘ou’. I didn’t include them in the check for DangerousRights because for starters Microsoft doesn’t allow linking GPOs to them and also because I wouldn’t delegate privileges to them.

We showed how to change the default location for new computers here, as well as changing the default that allows Domain Users to add workstations to the domain. That howto also showed how to utilize Group Policy to mitigate against the helpdesk being human and forgetting to create the computer’s account in AD before adding the workstation to the domain.

There have been more than a few security issues that have surfaced and keep surfacing that come out of the default configuration that allows mere Domain Users to add computers to the domain.

Summary

It’s important to make sure some prior sysadmin didn’t get sloppy and give non default groups the privileges to modify existing GPOs on SYSVOL. It is also important to audit who can link new GPOs. Attackers know this stuff and are checking for it once they gain an initial foothold. Just ask Colonial Pipelines …

I tweaked Trimarc’s idea to check the ACLs of users and groups for non-whitelisted entities as well. I’ll spin that one off into a separate howto.

References

LockBit 2.0 RaaS abusing GPOs: https://www.bleepingcomputer.com/news/security/lockbit-ransomware-now-encrypts-windows-domains-using-group-policies/

EMA finds attackers targeting AD: https://www.attivonetworks.com/ransomware-groups-are-driving-active-directory-exploitation-to-unacceptable-rates/

GPO abuse in general: https://pentestmag.com/gpo-abuse-you-cant-see-me/

Sean Metcalf on GPOs: https://adsecurity.org/?p=3658

Easy365Manager on OUs: https://www.easy365manager.com/how-to-document-ou-delegation/

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/

Microsoft docs on generic containers: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc978249(v=technet.10)

Colonial Pipelines attack explained: https://securityboulevard.com/2021/05/the-colonial-pipeline-ransomware-attack-everything-we-know/

Colonial Pipelines attack explained in detail: https://areteir.com/article/darkside-ransomware-caviar-taste-on-your-big-game-budget/

Group Policy is important: https://blog.paramountdefenses.com/2021/07/at-heart-of-colonial-pipeline-hack.html

--

--

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.