Skip to content
06 · Group Policy & SYSVOLPart 2 of 4Intermediate

Auditing GPO permissions and gPLink rights

Find who can edit, create and link GPOs in Active Directory: GPO ACLs, gPLink rights on OUs and sites, Group Policy Creator Owners and WMI filters.

Florian Amette7 min read

A GPO is code execution on every computer it applies to. A scheduled task, a startup script or a restricted groups entry pushed through a GPO linked to the Domain Controllers OU runs as SYSTEM on every DC within the next refresh cycle. That makes three permissions Tier 0 whenever they touch Tier 0 systems: the right to edit a GPO, the right to link a GPO to an OU, site or domain, and the right to create GPOs in the first place. Attack path tools such as BloodHound routinely surface GpoEdit or gPLink write rights held by helpdesk groups, and they are among the most common routes to domain compromise found in assessments.

The Group Policy and SYSVOL pillar introduces the edit-versus-link distinction with a quick check of the domain root and DC OU. This guide goes further: every GPO, every OU and site, GPO ownership, the creator group, WMI filters, and the join between them that tells you which findings are actually Tier 0.

How GPO permissions are stored

A GPO has two halves. The Group Policy Container (GPC) is an AD object at CN={GUID},CN=Policies,CN=System,<domain DN>; the Group Policy Template (GPT) is the folder \\<domain>\SYSVOL\<domain>\Policies\{GUID}. The Group Policy Management Console (GPMC) and the GroupPolicy PowerShell module write matching permissions to both, expressed as five levels: GpoRead, GpoApply, GpoEdit, GpoEditDeleteModifySecurity and GpoCustom. Anyone with write access to either half can change what the policy does.

Link rights are separate and live on the target: write access to the gPLink attribute (schema GUID f30e3bbe-9ff0-11d1-b603-0000f80367c1) on an OU, domain or site object decides which GPOs apply there. gPOptions (f30e3bbf-9ff0-11d1-b603-0000f80367c1) controls Block Inheritance. GenericAll, GenericWrite, WriteDacl and WriteOwner on the OU imply both.

Measure: map GPOs to what they reach

Start with the join that decides severity: which GPOs apply to Tier 0 systems. At minimum, include the Domain Controllers OU, the domain root, and any OU holding Tier 0 servers (AD CS, Entra Connect, backup, PAM) from your Tier 0 inventory.

PowerShell
Import-Module GroupPolicy, ActiveDirectory
$domain = Get-ADDomain
$tier0Targets = @(
    $domain.DomainControllersContainer
    $domain.DistinguishedName
    'OU=Tier0 Servers,OU=Admin,DC=corp,DC=example'   # adjust to your layout
)

$tier0Gpos = foreach ($t in $tier0Targets) {
    (Get-GPInheritance -Target $t).InheritedGpoLinks |
        Select-Object @{n='Target';e={$t}}, DisplayName, GpoId, Enforced
}
$tier0Gpos | Sort-Object DisplayName -Unique | Format-Table -AutoSize

InheritedGpoLinks includes GPOs linked higher up and enforced links, so it reflects what actually applies. Site-linked GPOs also apply to DCs in that site; list them with the gPLink attribute on CN=Sites,CN=Configuration,... objects (see below).

Audit: GPO edit rights and ownership

List every non-default trustee with edit-level access or ownership, and flag the GPOs that reach Tier 0:

PowerShell
$defaultTrustees = 'Domain Admins','Enterprise Admins','SYSTEM','ENTERPRISE DOMAIN CONTROLLERS','Authenticated Users'
$tier0Ids = $tier0Gpos.GpoId | ForEach-Object { $_.ToString() }

Get-GPO -All | ForEach-Object {
    $gpo = $_
    $perms = Get-GPPermission -Guid $gpo.Id -All |
        Where-Object { $_.Permission -in 'GpoEdit','GpoEditDeleteModifySecurity','GpoCustom' -and
                       $_.Trustee.Name -notin $defaultTrustees }
    foreach ($p in $perms) {
        [pscustomobject]@{
            GPO        = $gpo.DisplayName
            Tier0      = $tier0Ids -contains $gpo.Id.ToString()
            Trustee    = "$($p.Trustee.Domain)\$($p.Trustee.Name)"
            Permission = $p.Permission
            Owner      = $gpo.Owner
        }
    }
} | Sort-Object Tier0, GPO -Descending | Format-Table -AutoSize

GpoCustom needs a manual look at the underlying ACE, because it can hide a single WriteProperty or WriteDacl. Also review the Owner column separately: the owner of the GPC has implicit rights to change its permissions. GPOs created by members of Group Policy Creator Owners are owned by that member, not by Domain Admins.

GPMC also reports when the AD and SYSVOL permissions disagree ("The permissions for this GPO in the SYSVOL folder are inconsistent"). An inconsistency usually means someone edited the NTFS ACL or the AD ACL directly; check the GPT folder when the GPC looks clean:

PowerShell
$gpoPath = "\\$($domain.DNSRoot)\SYSVOL\$($domain.DNSRoot)\Policies"
foreach ($id in $tier0Ids) {
    (Get-Acl "$gpoPath\{$id}").Access |
        Where-Object { $_.FileSystemRights -match 'Write|Modify|FullControl|ChangePermissions|TakeOwnership' } |
        Select-Object @{n='GPO';e={$id}}, IdentityReference, FileSystemRights, IsInherited
}

Next, find every non-admin principal that can write gPLink anywhere. Filter out well-known admin SIDs by suffix rather than by name, so that localised group names do not slip through.

PowerShell
$gpLink  = [guid]'f30e3bbe-9ff0-11d1-b603-0000f80367c1'
$adminRx = '-(512|518|519)$|^S-1-5-18$|^S-1-5-32-544$|^S-1-5-9$'   # DA, Schema Admins, EA, SYSTEM, Administrators, EDCs
$configNC = (Get-ADRootDSE).configurationNamingContext

$targets = @($domain.DistinguishedName) +
           (Get-ADOrganizationalUnit -Filter * | Select-Object -ExpandProperty DistinguishedName) +
           (Get-ADObject -SearchBase "CN=Sites,$configNC" -LDAPFilter '(objectClass=site)' |
                Select-Object -ExpandProperty DistinguishedName)

foreach ($t in $targets) {
    (Get-Acl "AD:\$t").Access | Where-Object {
        $_.AccessControlType -eq 'Allow' -and (
            ($_.ActiveDirectoryRights -match 'WriteProperty' -and $_.ObjectType -in $gpLink, [guid]::Empty) -or
            $_.ActiveDirectoryRights -match 'GenericAll|GenericWrite|WriteDacl|WriteOwner')
    } | ForEach-Object {
        $sid = try { $_.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value } catch { $_.IdentityReference.Value }
        if ($sid -notmatch $adminRx) {
            [pscustomobject]@{
                Target    = $t
                Trustee   = $_.IdentityReference.Value
                Rights    = $_.ActiveDirectoryRights
                Inherited = $_.IsInherited
            }
        }
    }
} | Sort-Object Target | Format-Table -AutoSize

Expect some legitimate delegations on departmental OUs. Anything on the domain root, the Domain Controllers OU, a site that contains DCs, or an OU holding Tier 0 servers is a finding. Remember that site-linked GPOs apply to every computer in the site, DCs included, so write access to a site object is as sensitive as the domain root. For the broader question of which other ACEs on those OUs matter, see auditing AD ACLs.

Audit: GPO creation and WMI filters

PowerShell
Get-ADGroupMember 'Group Policy Creator Owners' -Recursive | Select-Object Name, objectClass

# Who can create GPOs directly on the Policies container
$policies = "CN=Policies,CN=System,$($domain.DistinguishedName)"
(Get-Acl "AD:\$policies").Access |
    Where-Object { $_.ActiveDirectoryRights -match 'CreateChild|GenericAll' } |
    Select-Object IdentityReference, ActiveDirectoryRights

# WMI filters: write access changes targeting of every GPO that uses them
$som = "CN=SOM,CN=WMIPolicy,CN=System,$($domain.DistinguishedName)"
Get-ADObject -SearchBase $som -LDAPFilter '(objectClass=msWMI-Som)' -Properties msWMI-Name |
    ForEach-Object {
        $f = $_
        (Get-Acl "AD:\$($f.DistinguishedName)").Access |
            Where-Object { $_.ActiveDirectoryRights -match 'Write|GenericAll' } |
            Select-Object @{n='Filter';e={$f.'msWMI-Name'}}, IdentityReference, ActiveDirectoryRights
    }

Group Policy Creator Owners should be empty. A writable WMI filter lets its editor widen a GPO's scope, for example to make a helpdesk-managed workstation GPO also apply to servers.

Reading the results: common findings

The same handful of patterns account for most findings in real domains. Knowing them speeds up triage.

  • Helpdesk or server team with GpoEdit on the Default Domain Policy or Default Domain Controllers Policy. Usually granted years ago so a team could change one setting. Both GPOs reach Tier 0, so this is equivalent to Domain Admin. Move the setting they need into a GPO scoped to their own OU and remove the right.
  • A former administrator as GPO owner. Ownership survives group membership changes and even account renames. If that account is disabled, the risk is low but the ownership still blocks clean audits; if it is still enabled, it holds implicit rights over the GPO's permissions.
  • Authenticated Users or Domain Users with GpoEdit. Almost always a mistake made while configuring security filtering, where someone picked the wrong permission level. It is a critical finding if the GPO is linked anywhere.
  • Service accounts with link rights. Deployment and provisioning tools are sometimes given broad write access to OUs so they can move computers, which silently includes gPLink. Scope their delegation to the specific object types and attributes they need.
  • Inherited GenericAll on an OU tree. A delegation made at a parent OU flows to every child, including any Tier 0 OU someone later created underneath it. Tier 0 OUs should block inheritance of delegated ACEs or sit outside delegated trees entirely.

Treat every finding on a GPO that appears in the Tier 0 join from the Measure step as a Tier 0 finding, whatever the GPO's name suggests.

Enforce: remove and re-delegate

Remove non-admin edit rights on Tier 0 GPOs with the GroupPolicy module, which updates both GPC and GPT:

PowerShell
Set-GPPermission -Name 'Default Domain Controllers Policy' -TargetName 'CORP\Helpdesk' `
    -TargetType Group -PermissionLevel None -Replace

Remove a gPLink delegation from an OU by removing the specific ACE rather than every ACE for the trustee:

PowerShell
$ou  = 'OU=Tier0 Servers,OU=Admin,DC=corp,DC=example'
$acl = Get-Acl "AD:\$ou"
$acl.Access | Where-Object { $_.IdentityReference -eq 'CORP\Helpdesk' -and $_.ObjectType -eq $gpLink -and -not $_.IsInherited } |
    ForEach-Object { [void]$acl.RemoveAccessRule($_) }
Set-Acl -Path "AD:\$ou" -AclObject $acl

If the ACE is inherited, fix it at the parent where it is defined. Then re-delegate the right way: departmental admins get GpoEdit on their own GPOs and link rights on their own OU only (GPMC > the OU > Delegation tab > Link GPOs), and Tier 0 GPOs are edited only from a privileged access workstation by Tier 0 admins. Take ownership of GPOs owned by former creators and assign ownership to Domain Admins.

Verify

Rerun the three audit blocks; the Tier 0 rows should now list only default admin trustees. Confirm with Get-GPPermission -Name '<GPO>' -All on each Tier 0 GPO and check GPMC shows no SYSVOL inconsistency warnings. Then make drift visible: enable "Audit Directory Service Changes" on DCs and add SACLs for write on groupPolicyContainer objects and on gPLink for OUs. Events 5136 (attribute modified, including gPLink and versionNumber), 5137 (GPO created) and 5141 (GPO deleted) will then record every change; the AD event ID reference covers how to read them.

What it breaks

  • Removing helpdesk or site admin edit rights on shared GPOs stops them making changes they previously self-served. Give them their own GPOs, scoped to their OUs.
  • Removing gPLink rights means delegated admins can no longer attach GPOs to OUs outside their scope, and anything they had linked higher up keeps working until someone unlinks it; review existing links too.
  • Emptying Group Policy Creator Owners blocks GPO creation for those members; creation must go through the chartered group.
  • Taking ownership of GPOs removes the implicit WriteDacl the old owner had, which can break automation that ran as that account and modified permissions.
  • Changing security filtering while cleaning up: since MS16-072 (June 2016), computers read GPOs in their own security context, so if you remove Authenticated Users from a GPO's ACL you must leave Read for Domain Computers (or the target computers) or the GPO silently stops applying.
  • Tightening WMI filter ACLs stops non-admins from adjusting targeting, which some teams use for OS version rollouts.

Related reading: the Group Policy & SYSVOL topic hub, attack path management to prioritise GPO edges in the wider graph, and removing GPP passwords for the other long-standing GPO finding.

Frequently asked questions

Is edit access to an unlinked GPO a risk?

Less than edit access to a linked one, but not zero. Anyone who can link GPOs to an OU can later link that GPO somewhere sensitive, and unlinked GPOs are often forgotten, so their permissions are never reviewed. Either delete GPOs that are not linked anywhere and not needed, or keep their ACLs to the same standard as linked ones.

Should Group Policy Creator Owners have any members?

In most domains it should be empty. Members can create new GPOs and become the owner, and therefore have full control, of what they create. That is harmless on its own, but combined with link rights on any OU it becomes a way to push settings or scripts to those computers. Grant GPO creation to a dedicated, Tier-appropriate group through the Group Policy Objects container delegation instead.

How often should I rerun a GPO permissions audit?

Run the full audit quarterly and after any reorganisation of OUs or admin groups, and monitor continuously with directory service change auditing. Events 5136 on groupPolicyContainer objects and on OU gPLink attributes tell you when a GPO or link changes, so the quarterly audit catches permission drift while monitoring catches the changes that actually use it.

Auditing GPO permissions and gPLink rights

Related guides

Group Policy & SYSVOL

Group Policy and SYSVOL hardening

Lock down GPO delegation, purge Group Policy Preferences cpassword secrets from SYSVOL, and add change control to prevent silent GPO abuse.

Foundation
Group Policy & SYSVOL

Deploying Microsoft security baselines with GPO

Deploy Microsoft security baselines with Group Policy: SCT, Policy Analyzer gap analysis, LGPO testing, ring-based rollout, exceptions and drift checks.

Intermediate