ADGPO-015: Scheduled Tasks in GPOs
- Platform
- Active Directory
- Category
- AD Group Policy
- Severity
- High
- Zero Trust pillar
- Governance (weight 1)
- Golden fixtures
- 3
- Branch coverage
- Observed: fixtures prove the verdicts they exercise
- Provenance
- baseline
What it checks
For every domain in the forest the check enumerates Group Policy Preferences scheduled task XML files at four canonical SYSVOL paths under each GPO at \\<domain>\SYSVOL\<domain>\Policies\{GUID}\: - Machine\Preferences\ScheduledTasks\ScheduledTasks.xml (computer-scope tasks and immediate tasks) - User\Preferences\ScheduledTasks\ScheduledTasks.xml (user-scope tasks and immediate tasks) Each XML file is parsed for the four element types defined by the Preferences scheduled-task client-side extension: <Task> (legacy Windows 2000/XP/2003 task), <TaskV2> (Vista and later, the modern format), <ImmediateTask>, and <ImmediateTaskV2>. For every task element the check projects: name, action (Create / Update / Replace / Delete), uid, changed timestamp, runAs principal (RunAs attribute and runLevel), the Triggers collection (logon, scheduled, idle, event), and most importantly every Action child: Command (the executable path), Arguments, WorkingDirectory, plus the Author and userContext attributes on TaskV2. The check also reads the cpassword attribute on the top-level <Properties> element of any <Task> or <ScheduledTask> entry (Group Policy Preferences historically stored a reversibly encrypted password there before MS14-025 / CVE-2014-1812). For every task the check resolves the parent groupPolicyContainer object, reads its DisplayName, distinguishedName, gPCFileSysPath, and nTSecurityDescriptor, enumerates every OU that links the GPO via gPLink, and emits a single denormalized record per task per scope of management. The check additionally inspects the file system DACL on the Command target and on any UNC path embedded in Command, Arguments, or WorkingDirectory so the operator can immediately see whether the binary the task launches lives on an attacker-writable share.
Why it matters
A Group Policy Preferences scheduled task is the simplest forest-wide code execution and persistence primitive Active Directory exposes. The Group Policy Preferences client-side extension (gpprefcl.dll, CSE GUID AADCED64-746C-4633-A97C-D61349046527) runs on every Group Policy refresh, parses ScheduledTasks.xml, and calls the Task Scheduler API to create or update each defined task on every targeted computer or for every targeted user. The CSE runs in the local SYSTEM security context on the client, which means it can create tasks that run as SYSTEM, NetworkService, LocalService, or any named principal regardless of whether that principal is present or logged on. An <ImmediateTaskV2> with an action of Create fires once at the next refresh and then deletes itself, leaving no Scheduled Tasks library entry on the endpoint and only a thin trail in the GPP CSE operational log; a recurring <TaskV2> persists as a registered task across reboots and Group Policy refreshes. The blast radius is whatever OU the parent GPO is linked to. A scheduled-task GPO linked to the Domain Controllers OU runs SYSTEM on every DC, which is equivalent to DCSync, DSRM credential theft, and direct ntds.dit access. A GPO linked to the domain root runs on every joined member. A GPO linked to a Tier-0 OU pivots straight to Tier 0. The principal that creates or edits the task only needs Edit settings on the parent GPO (the WriteProperty right on gPCFileSysPath plus write access to the SYSVOL subtree). The principal that triggers execution needs no rights at all on the target endpoint: the CSE on the endpoint executes the task locally. Three additional risk vectors compound this. First, any task that stores credentials in the cpassword attribute discloses those credentials directly from SYSVOL to every Authenticated User, because SYSVOL is world-readable by default and the AES key used to wrap cpassword was published by Microsoft in MSDN (CVE-2014-1812 / MS14-025). Second, any task whose Command points to a UNC path or to a writable directory (typical findings: C:\ProgramData\<vendor>\agent.exe, \\fileserver\deploy\install.bat, or a vendor folder where local Users have Modify) lets an attacker who controls that path swap the binary and inherit the SYSTEM context of every execution. Third, the legacy <Task> element predates secure scheduling and is interpreted by older Task Scheduler 1.0 APIs that ignore many modern hardening flags. Together these mean an over-delegated GPO plus a scheduled-task item plus a writable Command path is a one-step path to forest takeover.
Attack path
Step 1: Discovery. The attacker, holding any authenticated foothold, enumerates GPOs that ship scheduled tasks. Direct LDAP query for (&(objectClass=groupPolicyContainer)(gPCMachineExtensionNames=*AADCED64-746C-4633-A97C-D61349046527*)) returns every GPO with the Preferences CSE registered on the computer side; the equivalent query on gPCUserExtensionNames returns user-scope tasks. For each hit the attacker enumerates \\<domain>\SYSVOL\<domain>\Policies\{GUID}\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml directly over SMB. BloodHound surfaces the GPO -> OU -> Computer edges so the attacker immediately knows which DCs, servers, and workstations are in scope. Step 2: Content triage. For each ScheduledTasks.xml the attacker extracts Command, Arguments, WorkingDirectory, RunAs, and the cpassword attribute on every Task element. Any cpassword is decrypted in seconds with Get-GPPPassword from PowerSploit or with a one-line AES decrypt using the published MSDN key, yielding plaintext credentials for whatever account was scheduled (frequently a domain account with local-admin rights everywhere the GPO applied). Step 3: Hijack-by-path. The attacker inspects the file-system DACL on every Command path. Where Command resolves to a vendor folder with weak permissions (Authenticated Users:Modify, local Users:Write, or a UNC on an open share), the attacker overwrites the binary. The next Group Policy refresh on every targeted machine executes the attacker payload as the configured RunAs principal, typically SYSTEM. Step 4: Edit-the-GPO. Where the parent GPO is editable by a non Tier-0 group (a help desk group, an application team, a stale service account, or any principal with WriteProperty on gPCFileSysPath), the attacker uses SharpGPOAbuse --AddComputerTask or PowerView Set-DomainObject to write a new <ImmediateTaskV2> into ScheduledTasks.xml. The task action is Create, the Command is the attacker payload, RunAs is NT AUTHORITY\SYSTEM, and Triggers is empty (ImmediateTaskV2 runs once at next refresh and self-deletes). The attacker bumps versionNumber on the GPC and Version= in GPT.ini so clients pick up the change on the next refresh. Step 5: Forced refresh. Optional. The attacker invokes Invoke-GPUpdate -Force or schedules a remote gpupdate via WinRM, WMI, or Scheduled Tasks itself on any reachable target to collapse the dwell time from the default 90-to-120-minute refresh window down to seconds. Domain controllers run gpupdate every 5 minutes by default, so a DC-targeted task fires almost immediately. Step 6: Persistence. A recurring <TaskV2> registered as a hidden user-context task survives reboot, refresh, and remediation of the parent GPO if defenders only delete the XML on SYSVOL: tasks already installed on endpoints continue to fire from the local Task Scheduler database until each endpoint receives an explicit Delete action via the same CSE. The attacker can therefore plant the task, remove the XML, and the persistence remains. Hunt operators must enumerate local scheduled tasks on every endpoint to find the surviving copies. Step 7: Cleanup. The attacker reverts the GPO edit and lets the next refresh remove the staging task from any endpoint that has not yet executed it. The recurring hidden task remains in place on every endpoint that fetched the policy.
How Guerrilla assesses it
Guerrilla enumerates every domain in the forest, queries (&(objectClass=groupPolicyContainer)) under CN=Policies,CN=System,DC=<domain>, and for each GPO walks the SYSVOL path returned by gPCFileSysPath. The check tests the four canonical Preferences scheduled-task XML paths (Machine\Preferences\ScheduledTasks\ScheduledTasks.xml, User\Preferences\ScheduledTasks\ScheduledTasks.xml, plus the legacy IEAK and SchedulingAgent variants where present) and loads each one with [xml]::new(). It iterates every <Task>, <TaskV2>, <ImmediateTask>, and <ImmediateTaskV2> child, projects the Properties attributes (name, action, runAs, cpassword, logonType), and parses the nested <Triggers> and <Actions> collections to extract Command, Arguments, WorkingDirectory, and Author for each scheduled action. For every task the check resolves the parent groupPolicyContainer object, reads gPCMachineExtensionNames / gPCUserExtensionNames to confirm the Preferences CSE GUID AADCED64-746C-4633-A97C-D61349046527 is registered on the matching side, reads nTSecurityDescriptor on the GPC object, and walks every OU in the domain for gPLink values that reference the GPO. The output record contains, per task per linked OU: GPO display name and GUID, task name, action verb, runAs principal and resolved SID, the literal Command and Arguments strings, the resolved file-system DACL on the Command target (with explicit ACE entries for any principal holding Write, Modify, or FullControl), the parent OU DN, and the link-enabled / enforced flags. The check flags the following as findings: any task with a non-empty cpassword attribute (CVE-2014-1812), any task whose RunAs resolves to a privileged group member or a service account not listed in the documented inventory, any task whose Command path is writable by a non-administrator principal, any task whose Command embeds a UNC pointing at a share with weak ACLs, any <Task> element using the legacy schema, and any task whose parent GPO is linked to the Domain Controllers OU or to a Tier-0 OU.
Recommended value
All GPO-deployed scheduled tasks documented, using least-privilege accounts, and performing authorized operations only
Remediation
Review all Scheduled Task items in GPO Preferences across all GPOs. Verify each task runs a legitimate and authorized command with the minimum required privileges. Remove any tasks that store credentials (use gMSA or SYSTEM context instead). Ensure task executables are stored in protected locations. Document the business purpose for each GPO-deployed scheduled task.
Fixture-proven verdicts
Every verdict below is proven by a golden fixture in the module's gating test suite. This table derives from the last green run; it cannot be edited by hand.
| Scenario | Expected verdict |
|---|---|
| clean | PASS |
| known-bad | WARN |
| throttled | Not Assessed |
Framework mappings
- NIST SP 800-53
- CM-6, CM-5, AC-6
- MITRE ATT&CK
- T1053.005, T1484.001