ADGPO-007: GPO Permission Inconsistencies
- Platform
- Active Directory
- Category
- AD Group Policy
- Severity
- High
- Zero Trust pillar
- Governance (weight 2)
- Golden fixtures
- 3
- Branch coverage
- Observed: fixtures prove the verdicts they exercise
- Provenance
- baseline
What it checks
For each GPO returned by Get-GPO -All, the check resolves two security descriptors. First, the AD security descriptor on the GPC object at CN={GUID},CN=Policies,CN=System,DC=domain,DC=tld, read via Get-Acl AD:\<distinguishedName> or the nTSecurityDescriptor LDAP attribute and converted with [System.DirectoryServices.ActiveDirectorySecurity]. Second, the NTFS security descriptor on the GPT folder at \\domain\SYSVOL\domain\Policies\{GUID}, read via Get-Acl on the UNC path. The check then enumerates principals on both sides and compares effective rights for each well-known role required by the Group Policy engine: Authenticated Users must have Read on both (GenericRead / ApplyGroupPolicy on the GPC, ReadAndExecute on the GPT), Domain Admins and Enterprise Admins must have Full Control on both, Group Policy Creator Owners must have CreateChild on the container and full rights on GPOs they created, and SYSTEM must have Full Control on both. A GPO is flagged when any of the following is true: a principal has Modify or Write on the GPT but no equivalent AD WriteProperty on the GPC; Authenticated Users is missing Read on either side; SYSVOL grants Everyone, Authenticated Users Modify, or any low-privileged identity Write; the GPC owner does not match the GPT owner; or dcdiag /test:sysvolcheck and /test:netlogons report mismatches. The output also captures GPO display name, GUID, the specific principal and right combination that drifted, and which side of the pair is more permissive.
Why it matters
The AD GPC and the SYSVOL GPT are two halves of one trust boundary, and the client-side Group Policy engine enforces the weaker of the two. If SYSVOL is more permissive than AD, an attacker with file-system write access to a GPT folder can drop a malicious GptTmpl.inf (Restricted Groups, User Rights Assignment, Privilege Rights), a poisoned Registry.pol, an Immediate Scheduled Task XML under ScheduledTasks\ScheduledTasks.xml, or a startup script under Machine\Scripts\Startup, and the next gpupdate on every targeted machine will execute it as SYSTEM. The AD-side audit trail shows nothing: no GPC modification, no Event ID 5136, no gPLink change. Defenders who only watch directory service auditing see a quiet GPO and miss code execution on every domain-joined host the GPO applies to. The inverse failure (AD more permissive than SYSVOL) breaks GPO application: clients read the GPC, follow the gPCFileSysPath pointer to SYSVOL, get access denied on Registry.pol, and the entire GPO silently fails with Event ID 1058 in the operational log. Critical security baselines (Default Domain Controllers Policy, LAPS, AppLocker, attack surface reduction rules) stop applying and the estate drifts out of compliance without anyone noticing. The check also catches the canonical SYSVOL hardening regression where MS16-072 / KB3163622 enforcement requires Authenticated Users to retain Read on the GPC but operators sometimes strip it expecting the old user-context evaluation, breaking computer-scope application across the entire domain.
Attack path
1. Discovery: the attacker enumerates GPOs with Get-GPO -All or PowerView Get-DomainGPO and pulls both ACLs. Get-DomainObjectAcl -SearchBase "CN=Policies,CN=System,DC=corp,DC=contoso,DC=com" returns the AD side. Get-Acl on \\corp.contoso.com\SYSVOL\corp.contoso.com\Policies\{GUID} returns the NTFS side. BloodHound edges GenericAll, GenericWrite, WriteDacl, WriteOwner on GPO nodes surface the AD-side rights. SharpHound -CollectionMethod GPOLocalGroup walks GPT contents. 2. Target selection: the attacker filters for GPOs where SYSVOL grants a non-privileged principal Write or Modify but the GPC does not. Common drift sources are help-desk groups historically granted Modify on SYSVOL for script management, decommissioned service accounts left in the DACL, project teams given SYSVOL access during a migration and never removed, and GPOs created by tools that set NTFS but not AD permissions correctly. 3. Weaponization: the attacker writes directly to the GPT folder. To force a Restricted Groups membership add, edit Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf and add a [Group Membership] section with *S-1-5-32-544__Members = attacker SID. To run code as SYSTEM on every targeted machine, drop ScheduledTasks\ScheduledTasks.xml with an Immediate Scheduled Task (T1053.005). To establish persistence on logon, drop User\Scripts\Logon\beacon.ps1 and register it in scripts.ini. To bump the version so clients re-apply, increment the Version value in gpt.ini and the versionNumber attribute on the GPC (the latter requires AD write, which the attacker does not have, so they instead wait for the next 90-minute refresh which still re-reads the GPT). 4. Replication: DFSR (or FRS on legacy estates) replicates the new GPT contents to every DC. No GPC modification event fires because nothing in AD changed. The only telemetry is SYSVOL file-system auditing, which is rarely enabled. 5. Execution: at the next Group Policy refresh, every machine in the linked OU pulls the GPT, applies the malicious Restricted Groups / Scheduled Task / startup script, and executes the payload as SYSTEM. For a GPO linked at the Domain Controllers OU, this is code execution on every DC. 6. Cleanup: the attacker reverts the GPT files to their pre-attack state, replicates again, and leaves no AD trail. To a defender skimming GPMC, the GPO never changed.
How Guerrilla assesses it
Guerrilla loads the GroupPolicy and ActiveDirectory modules, enumerates GPOs with Get-GPO -All, and for each GPO reads two security descriptors. The AD side uses Get-Acl "AD:\CN={$gpoId},CN=Policies,CN=System,$($domainDN)" to materialise an ActiveDirectorySecurity object; the access rules are normalised to (IdentityReference, ActiveDirectoryRights, AccessControlType, ObjectType, InheritedObjectType) tuples. The NTFS side uses Get-Acl "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpoId)}" to materialise a FileSystemSecurity object; rules are normalised to (IdentityReference, FileSystemRights, AccessControlType, InheritanceFlags). The check then performs three comparisons. (a) Required-principal presence: Authenticated Users, SYSTEM, Domain Admins, Enterprise Admins, and Creator Owner must appear with the expected minimum rights on both sides; missing entries are flagged. (b) Drift detection: every non-built-in principal present on one side is searched on the other, and an asymmetry is flagged when a principal has Write/Modify/Full Control on SYSVOL with no equivalent Write/GenericWrite on the GPC, or vice versa. (c) Hardening violations: any GPT ACE granting Everyone, Anonymous Logon, or Authenticated Users beyond ReadAndExecute is flagged as critical. The check also shells out to dcdiag /test:sysvolcheck /test:netlogons and parses the pass/fail status per DC, surfacing replication-level inconsistencies that an ACL diff alone would miss. Output rows include DisplayName, Id, OwnerAd, OwnerSysvol, OwnersMatch (bool), AuthUsersReadAd (bool), AuthUsersReadSysvol (bool), DriftedPrincipals (array of @{Identity, AdRights, SysvolRights, MoreOpenSide}), HardeningViolations (array), DcdiagStatus, and Severity.
Recommended value
Consistent permissions between AD GPC objects and SYSVOL GPT folders for all GPOs; Authenticated Users have Read access
Remediation
Compare the security descriptor on each GPC object in AD with the NTFS ACL on the corresponding GPT folder in SYSVOL. Ensure that both grant Read access to Authenticated Users (required for GPO application). Resolve any inconsistencies by aligning SYSVOL permissions with the AD object. Run dcdiag /test:sysvolcheck to identify issues.
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 | FAIL |
| throttled | Not Assessed |
Framework mappings
- NIST SP 800-53
- AC-3, CM-6
- MITRE ATT&CK
- T1484.001, T1222.001