ADGPO-024: GPO WMI Filter Review

Platform
Active Directory
Category
AD Group Policy
Severity
Low
Zero Trust pillar
Governance (weight 1)
Golden fixtures
2
Branch coverage
Observed: fixtures prove the verdicts they exercise
Provenance
baseline

What it checks

WMI filters are stored as msWMI-Som objects under CN=SOM,CN=WMIPolicy,CN=System,DC=<domain>. Each filter object carries: msWMI-ID (the {GUID} that GPOs reference), msWMI-Name (display name), msWMI-Author (UPN of the creator at creation time), msWMI-CreationDate and msWMI-ChangeDate (FILETIME strings), and msWMI-Parm1 (description) and msWMI-Parm2 (the WQL payload, formatted as "<count>;3<namespace-len>;<namespace>4<query-len>;<query>"). The link between a filter and a GPO is the gPCWQLFilter attribute on the groupPolicyContainer object, which holds a string of the form "[<domain>;{<filter-guid>};0]". The check performs the following work for every WMI filter: it parses msWMI-Parm2 to extract the WMI namespace (usually root\CIMv2) and the WQL query, validates the query syntax against a WMI parser, classifies the query as Win32_OperatingSystem version check, Win32_ComputerSystem model check, registry/file probe via StdRegProv or CIM_DataFile, or a custom join. It then enumerates every GPO with gPCWQLFilter pointing at the filter, resolves the GPO link scope and the GPO display name, and inspects whether the GPO contains security-critical settings (audit policy, password policy, firewall, AppLocker, WDAC, LSA protections, RDP, SMB). For each (filter, GPO) pair the check records: FilterName, FilterGuid, Namespace, Query, Author, Created, Modified, LinkedGpos (count and names), LinkedSecurityCritical (bool), QueryClass, PerformanceRisk (CIM_DataFile and Win32_Product queries flagged High, registry probes flagged Medium, simple Win32_OperatingSystem flagged Low), and IsOrphaned (filter exists but no GPO references it). A filter is flagged when any of these conditions is true: the WQL query is malformed and silently fails (the engine treats a parse error as "no match" and skips the GPO); the filter is referenced by a security-critical GPO and the query is narrowly scoped to exclude a small set of systems by name, MAC, BIOS serial, or a registry marker; the filter calls Win32_Product (which triggers a full MSI consistency reinstall scan and adds tens of seconds of policy refresh latency); the filter is orphaned (no GPO references it but it persists in AD); the msWMI-Author or last modifier is outside the GPO administration team; or the filter was created or modified within an observation window that aligns with an incident.

Why it matters

WMI filtering is the only Group Policy mechanism that can selectively suppress a GPO based on data the target machine reports about itself at runtime. Unlike security filtering, which is visible as an explicit ACE on the GPO and surfaces immediately in GPMC, a WMI filter looks like a one-line description in the GPO Scope tab and the actual exclusion logic lives in a separate AD object that most reviewers never open. Three abuse patterns recur. First, defensive evasion: an attacker who has Modify rights on an existing WMI filter (or who can create a new one and re-bind a GPO to it) writes a query that excludes the systems they care about. For example, SELECT * FROM Win32_OperatingSystem WHERE NOT (CSName = "DC01" OR CSName = "DC02") attached to the Default Domain Controllers Policy will skip the policy on the two named DCs while leaving the policy linked and enabled, so GPMC, Get-GPInheritance, and most audit tools report the configuration as healthy. The same pattern silently disables Defender, audit policy, LSA protections, or AppLocker on chosen hosts. Second, denial of policy at scale: a query that always returns no rows (a deliberately false predicate, or a query against a class that does not exist) causes the GPO to be skipped on every machine, which is functionally equivalent to disabling the GPO without changing the link or the enabled flag. Reviewers see the GPO in the linked-and-enabled state and assume it applies. Third, performance and detection drag: queries against Win32_Product trigger a Windows Installer consistency check on every gpupdate, which is slow and writes thousands of MsiInstaller events that bury real signals; queries against CIM_DataFile with wildcards traverse the file system and can stall logon by minutes. Even where no attack is involved, undocumented WMI filters silently break hardening: an OS-version filter written for Windows 7 that was never updated will skip the GPO on every Windows 11 endpoint, leaving newly imaged machines unprotected. WMI filter abuse is a recognized sub-technique of Group Policy Modification (T1484.001), and Mandiant has documented APT actors using WMI to disable defenses (T1562.001) and to exclude monitored hosts from defensive policy.

Attack path

1. Discovery: with any domain-authenticated context, the attacker enumerates WMI filters and the GPOs that reference them. Get-ADObject -SearchBase "CN=SOM,CN=WMIPolicy,CN=System,$((Get-ADDomain).DistinguishedName)" -Filter "objectClass -eq 'msWMI-Som'" -Properties msWMI-Name,msWMI-Parm2,msWMI-Author,msWMI-ChangeDate dumps every filter. Get-GPO -All piped through Where-Object { $_.WmiFilter } maps GPOs to filters. PowerView Get-DomainGPO -Properties displayname,gpcwqlfilter exposes the same join. 2. Target selection: the attacker picks a high-value GPO that already has a WMI filter (modifying the existing filter avoids changing the GPO object itself, which would log Event ID 5136) or picks a high-value GPO with no filter and plans to bind one. 3. Permission abuse: WMI filter objects inherit ACLs from the WMIPolicy container by default. In many environments, Group Policy Creator Owners, helpdesk groups, or stale delegations have Write on msWMI-Som objects. The attacker confirms write access with Get-Acl on the filter DN, or by attempting Set-ADObject in a probe. 4. Modification of an existing filter: the attacker rewrites msWMI-Parm2 with a payload that excludes their target hosts. For the Default Domain Controllers Policy, a payload of "1;3;10;root\CIMv2;52;SELECT * FROM Win32_OperatingSystem WHERE CSName != 'DC01'" (followed by the correct length prefixes) skips the policy on DC01 only. No change to the GPO object, no change to SYSVOL, no Event ID 5136 on the GPO. Event ID 5136 does fire on the msWMI-Som object itself, but most SIEM rule sets do not monitor the WMIPolicy container. 5. Binding a new filter: alternatively, the attacker creates a new msWMI-Som object and sets the target GPO's gPCWQLFilter attribute to "[<domain>;{<new-filter-guid>};0]". This requires Write on the gPCWQLFilter property of the GPO, which Group Policy Creator Owners often have on GPOs they created. 6. Wait for refresh: at the next 90-minute Group Policy refresh, every targeted host evaluates the query, gets zero rows, and silently skips the GPO. gpresult /h shows the GPO as "Denied (WMI Filter)", but the reason is hidden unless the operator expands the filter detail and reads the WQL. 7. Cover: the attacker can leave the filter in place indefinitely. Periodic GPO review tools that only walk groupPolicyContainer objects and not msWMI-Som objects will report the GPO as correctly linked and enabled. Restoring the original filter after the operation, or deleting the new filter, removes the only artifact.

How Guerrilla assesses it

Guerrilla queries the WMI Policy container directly with Get-ADObject -SearchBase "CN=SOM,CN=WMIPolicy,CN=System,<domain-DN>" -LDAPFilter "(objectClass=msWMI-Som)" -Properties msWMI-Name,msWMI-ID,msWMI-Parm1,msWMI-Parm2,msWMI-Author,msWMI-CreationDate,msWMI-ChangeDate,nTSecurityDescriptor. The msWMI-Parm2 string is parsed by splitting on the length-prefixed fields ("<count>;3<n>;<namespace>4<n>;<query>") to extract namespace and query. Each WQL string is checked for syntactic validity, classified by the WMI class it targets (Win32_OperatingSystem, Win32_ComputerSystem, Win32_Processor, StdRegProv, CIM_DataFile, Win32_Product, custom), and tagged with a performance bucket. The check then runs Get-GPO -All and, for each returned GPO, reads the WmiFilter property and the underlying gPCWQLFilter attribute via Get-ADObject. The join produces a (FilterGuid, GpoName, GpoId, LinkScope) row set. The check cross-references the GPO display name and settings against a security-critical list (audit policy, password and lockout policy, Defender, AppLocker, WDAC, LSA, firewall, RDP, SMB signing) by parsing Get-GPOReport XML for the relevant CSE GUIDs. The nTSecurityDescriptor on each msWMI-Som object is inspected for non-default ACEs granting WriteProperty to non-Tier-0 principals. Output rows include FilterName, FilterGuid, Namespace, Query (truncated), QueryClass, PerformanceRisk, Author, Created, Modified, LinkedGpoCount, LinkedSecurityCritical (bool), IsOrphaned, NonDefaultAcl (bool), and a Notes field that enumerates failure reasons.

Recommended value

All WMI filters documented, tested, and producing expected results; no WMI filters that block security-critical GPOs

Remediation

Inventory all WMI filters using Get-ADObject -Filter 'objectClass -eq "msWMI-Som"'. Review the WQL query in each filter for correctness and test against representative target systems. Verify that WMI filters are not blocking security-critical GPOs from applying. Remove unused WMI filters. Document the purpose and expected behavior of each active WMI filter.

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.

Verdict scenarios for ADGPO-024
ScenarioExpected verdict
cleanPASS
throttledNot Assessed

Framework mappings

NIST SP 800-53
CM-6, CM-3
MITRE ATT&CK
T1484.001