Group Policy Objects (GPOs) is a subject I’ve wanted to write about for a long time and I’m happy to have finally started.
If you’re not too familiar with GPOs, I highly recommend you go and read A Red Teamer’s Guide to GPOs and OUs by Andy Robbins. He recaps how GPO enforcement works, how to use BloodHound to find GPO-control based attack paths, and explains a few ways to execute those attacks.
In terms of weaponisation, Will Schroeder made some ground when he published Abusing GPO Permissions and implemented New-GPOImmediateTask
into PowerView
. However, the function was later removed with the commment:
inconsistent and better done manually
The aim for this series of blog posts is to demonstrate how to enumerate these abuse opportunities; and exploit them for both privilege escalation and persistence purposes.
There are a couple of interesting permissions that we may want to look for. The ones that spring to mind are:
The reason I think of them this way, is because they’re permissions that are individually delegated. For example:
And so the combination of what permissions you have will depend on how you implement this abuse.
In the Group Policy Management Console (GPMC), delegated permission to create GPOs in the domain looks like this:
They can be easy to enumerate PowerView with the following:
PS > Get-DomainObjectAcl -SearchBase "CN=Policies,CN=System,DC=testlab,DC=local" -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "Group-Policy-Container" }
AceQualifier : AccessAllowed
ObjectDN : CN=Policies,CN=System,DC=testlab,DC=local
ActiveDirectoryRights : CreateChild <--- CreateChild just means "Create GPO" in this context
ObjectAceType : Group-Policy-Container
ObjectSID :
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106 <--- SID of the user/group
AccessMask : 1
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
PS > Convert-SidToName S-1-5-21-407754292-3742881058-3910138598-1106
LAB\Desktop Admins
Get-DomainOU
shows us all the Organizational Units
in AD. Here, we only have the default Domain Controllers
OU and a custom Workstations
OU.
PS > Get-DomainOU
usncreated : 6031
systemflags : -1946157056
iscriticalsystemobject : True
gplink : [LDAP://CN={6AC1786C-016F-11D2-945F-00C04fB984F9},CN=Policies,CN=System,DC=testlab,DC=local;0] <--- GUID(s) of GPO(s) already linked to the OU
whenchanged : 06/01/2019 13:14:24
objectclass : {top, organizationalUnit}
showinadvancedviewonly : False
usnchanged : 6031
dscorepropagationdata : {06/01/2019 13:15:24, 01/01/1601 00:00:01}
name : Domain Controllers
description : Default container for domain controllers
distinguishedname : OU=Domain Controllers,DC=testlab,DC=local
ou : Domain Controllers
whencreated : 06/01/2019 13:14:24
instancetype : 4
objectguid : d312c411-7c7c-4fb7-b4f9-cbf0637b551f
objectcategory : CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=testlab,DC=local
usncreated : 12790
name : Workstations
gplink : [LDAP://cn={7DD7A136-334C-47C1-8890-D9766D449EFA},cn=policies,cn=system,DC=testlab,DC=local;0] <--- GUID(s) of GPO(s) already linked to the OU
whenchanged : 07/01/2019 07:18:51
objectclass : {top, organizationalUnit}
usnchanged : 13118
dscorepropagationdata : {07/01/2019 07:18:51, 07/01/2019 07:17:22, 07/01/2019 07:14:37, 06/01/2019 13:52:27...}
distinguishedname : OU=Workstations,DC=testlab,DC=local
ou : Workstations
whencreated : 06/01/2019 13:28:56
instancetype : 4
objectguid : 4f733ab3-1809-4a31-b299-e07a3b7b4669
objectcategory : CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=testlab,DC=local
The Delegation of Control Wizard in Active Directory Users and Computers (ADUC) has a predefined template for “Manage Group Policy links”. It’s useful for delegating different types of privilege to principals over particular objects.
In this example, we delegate this to the LAB\Desktop Admins
group.
This is also easy to enumerate in PowerView by piping Get-DomainOu
into Get-DomainObjectAcl
and looking for the GP-Link
ACE.
PS > Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "GP-Link" }
AceQualifier : AccessAllowed
ObjectDN : OU=Workstations,DC=testlab,DC=local <--- The OU Distinguished Name
ActiveDirectoryRights : ReadProperty, WriteProperty <--- WriteProperty (GP-Link is a property on the OU object that you can see in the Attribute Editor of ADUC)
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit <--- This will be interesting later
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106 <--- SID of the user/group
AccessMask : 48
AuditFlags : None
IsInherited : False
AceFlags : ContainerInherit
InheritedObjectAceType : All
OpaqueLength : 0
We can also pipe Get-DomainGPO
into Get-DomainObjectAcl
to find which principals can modify them. Here we look for ActiveDirectoryRights
that match WriteProperty
, WriteDacl
or WriteOwner
. (In most cases we only expect to find WriteProperty
, but having WriteDacl
or WriteOwner
will allow us to grant WriteProperty
to ourselves and modify the GPO anyway).
We put a match
in for the SecurityIdentifier
so we only list RIDs > 1000 to avoid seeing Domain Admins
and Enterprise Admins
etc for every GPO.
PS > Get-DomainGPO | Get-DomainObjectAcl -ResolveGUIDs | Where-Object { $_.ActiveDirectoryRights -match "WriteProperty|WriteDacl|WriteOwner" -and $_.SecurityIdentifier -match "S-1-5-21-407754292-3742881058-3910138598-[\d]{4,10}" }
AceType : AccessAllowed
ObjectDN : CN={7DD7A136-334C-47C1-8890-D9766D449EFA},CN=Policies,CN=System,DC=testlab,DC=local
ActiveDirectoryRights : CreateChild, DeleteChild, Self, WriteProperty, DeleteTree, Delete, GenericRead, WriteDacl, WriteOwner
OpaqueLength : 0
ObjectSID :
InheritanceFlags : None
BinaryLength : 36
IsInherited : False
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1105 <--- SID of the user/group
AccessMask : 983295
AuditFlags : None
AceFlags : None
AceQualifier : AccessAllowed
AceType : AccessAllowed
ObjectDN : CN={7DD7A136-334C-47C1-8890-D9766D449EFA},CN=Policies,CN=System,DC=testlab,DC=local
ActiveDirectoryRights : CreateChild, DeleteChild, ReadProperty, WriteProperty, GenericExecute
OpaqueLength : 0
ObjectSID :
InheritanceFlags : ContainerInherit
BinaryLength : 36
IsInherited : False
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1109 <--- SID of the user/group
AccessMask : 131127
AuditFlags : None
AceFlags : ContainerInherit
AceQualifier : AccessAllowed
PS > Get-DomainGPO | Where-Object { $_.DistinguishedName -eq "CN={7DD7A136-334C-47C1-8890-D9766D449EFA},CN=Policies,CN=System,DC=testlab,DC=local" } | Select-Object DisplayName
displayname
-----------
Workstation Policy
As seen in the Details
tab of GPMC, LAB\bwallace
is the owner
of this GPO called Workstation Policy
.
Creators of a GPO are automatically granted explicit Edit settings, delete, modify security
, which manifests as CreateChild, DeleteChild, Self, WriteProperty, DeleteTree, Delete, GenericRead, WriteDacl, WriteOwner
.
Also in this example, LAB\tlockhart
has been granted explicit Edit settings
, which are CreateChild, DeleteChild, ReadProperty, WriteProperty, GenericExecute
.
This can be done from a couple of different angles. You could have an interesting GPO and want to know which OUs and/or computers that single GPO applies; you may want to list every GPO that applies to a particular OU; or you may want to list every GPO that applies to a particular computer.
Here, we list every GPO that applies to ws-1.testlab.local
- displaying only the Display Name
and GUID
name.
PS > Get-DomainGPO -ComputerIdentity ws-1 -Properties Name, DisplayName
displayname name
----------- ----
Demo GPO {ECB75201-82D7-49F3-A0E0-86788EE7DC36}
Workstation Policy {7DD7A136-334C-47C1-8890-D9766D449EFA}
Default Domain Policy {31B2F340-016D-11D2-945F-00C04FB984F9}
GPO’s are a bit funny since they have a Display Name, GUID Name and an Object GUID. The later two in particular are easy to confuse.
Here, we list every OU that the Demo GPO
applies to. We use the GUID name in the GPLink search filter.
PS > Get-DomainOU -GPLink "{ECB75201-82D7-49F3-A0E0-86788EE7DC36}" -Properties DistinguishedName
distinguishedname
-----------------
OU=Domain Controllers,DC=testlab,DC=local
OU=Workstations,DC=testlab,DC=local
If you then need to know which computers are in these OUs, you can do so with:
PS > Get-DomainComputer -SearchBase "LDAP://OU=Workstations,DC=testlab,DC=local" -Properties DistinguishedName
distinguishedname
-----------------
CN=WS-1,OU=Workstations,DC=testlab,DC=local
CN=WS-2,OU=Workstations,DC=testlab,DC=local
CN=WS-3,OU=Workstations,DC=testlab,DC=local
This one is more of a pig.
If we get the GPLink
attribute for the Workstations
OU (which tells us every GPO that is linked to it), the results are returned as a single text string which means we can’t just pipe it straight into Get-DomainGPO
and find their corresponding Display Names.
PS > Get-DomainOU -Identity "Workstations" -Properties GPLink
gplink
------
[LDAP://cn={ECB75201-82D7-49F3-A0E0-86788EE7DC36},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={7DD7A136-334C-47C1-8890-D9766D449EFA},cn=policies,cn=system,DC=test...
Instead, we can do it this way:
PS > $GPLink = (Get-DomainOU -Identity "Workstations" -Properties GPLink).gplink
PS > [Regex]::Matches($GPLink, '(?<={)(.*?)(?=})') | Select-Object -ExpandProperty Value | ForEach-Object { Get-DomainGPO -Identity "{$_}" -Properties DisplayName }
displayname
-----------
Demo GPO
Workstation Policy
The astute amongst you may notice in these examples, how some return GPOs included via inheritance (e.g. the Default Domain Policy) and others do not.
I find inheritance pretty interesting, especially when it comes to the Delegation of Control Wizard. By default, it will enable inheritance for This object and all descending objects
.
PS > Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs | Where-Object { $_.ObjectAceType -eq "GP-Link" }
AceQualifier : AccessAllowed
ObjectDN : OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit <---
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106
AccessMask : 48
AuditFlags : None
IsInherited : False <--- This OU *is not* inheriting from elsewhere
AceFlags : ContainerInherit <---
InheritedObjectAceType : All
OpaqueLength : 0
If we now subsequently create a new OU inside this one, LAB\Desktop Admins
will inherit the same GP-Link
privileges over it.
AceQualifier : AccessAllowed
ObjectDN : OU=DAs,OU=Workstations,DC=testlab,DC=local <--- DA OU is a child of Workstation OU
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit <---
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106
AccessMask : 48
AuditFlags : None
IsInherited : True <--- This OU *is* inheriting
AceFlags : ContainerInherit, Inherited <---
InheritedObjectAceType : All
OpaqueLength : 0
If we manually modify the inheritance on the Workstations
OU to This object only
, the new ACL looks like this:
AceQualifier : AccessAllowed
ObjectDN : OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : None <---
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106
AccessMask : 48
AuditFlags : None
IsInherited : False <---
AceFlags : None <---
InheritedObjectAceType : All
OpaqueLength : 0
Finally, if you have nested children like this:
The DA
OU will inherit from both Workstations
and Admins
. So we have a delegation on Workstations
for LAB\Desktop Admins
and a delegation on Admins
for LAB\Team 2
- the DA
OU will inherit both.
AceQualifier : AccessAllowed
ObjectDN : OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106
AccessMask : 48
AuditFlags : None
IsInherited : False
AceFlags : ContainerInherit
InheritedObjectAceType : All
OpaqueLength : 0
AceQualifier : AccessAllowed
ObjectDN : OU=Admins,OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1110
AccessMask : 48
AuditFlags : None
IsInherited : False
AceFlags : ContainerInherit
InheritedObjectAceType : All
OpaqueLength : 0
AceQualifier : AccessAllowed
ObjectDN : OU=Admins,OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106
AccessMask : 48
AuditFlags : None
IsInherited : True
AceFlags : ContainerInherit, Inherited
InheritedObjectAceType : All
OpaqueLength : 0
AceQualifier : AccessAllowed
ObjectDN : OU=DAs,OU=Admins,OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1110
AccessMask : 48
AuditFlags : None
IsInherited : True
AceFlags : ContainerInherit, Inherited
InheritedObjectAceType : All
OpaqueLength : 0
AceQualifier : AccessAllowed
ObjectDN : OU=DAs,OU=Admins,OU=Workstations,DC=testlab,DC=local
ActiveDirectoryRights : ReadProperty, WriteProperty
ObjectAceType : GP-Link
ObjectSID :
InheritanceFlags : ContainerInherit
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-407754292-3742881058-3910138598-1106
AccessMask : 48
AuditFlags : None
IsInherited : True
AceFlags : ContainerInherit, Inherited
InheritedObjectAceType : All
OpaqueLength : 0
I think that’s enough enumeration for now! In the next part, we’ll start with some abuses.