What an AD Empty Privileged Group Review Establishes
An AD empty privileged group review asks whether a reported absence of members is trustworthy and intentional. An informational result is a starting point for verification: identify the group, establish what the collection actually read, then record the owner's decision. Closing the finding should preserve that evidence rather than change membership merely to make the result disappear.
Microsoft documents several operator groups with no default members, including Account Operators, Backup Operators, Print Operators and Server Operators. Their empty state can therefore be expected. The same reference explains the groups' administrative capabilities and restrictions on deleting built-in groups. Consult the Active Directory security group reference for the specific group being reviewed.
The useful distinction is between unknown membership, members confirmed present, and intentional emptiness confirmed within a defined scope. These outcomes require different follow-up. A missing attribute, a failed command and an approved empty group must not share the same closure label.
How Membership Evidence Can Be Incomplete
The group object's member attribute is multi-valued and uses distinguished-name syntax. Reading it gives an attribute-level view. The review also needs to consider nested groups and, where relevant, account-side primary-group membership.
LDAP result paging and attribute range retrieval solve different problems. Paging retrieves batches of objects; attribute range retrieval retrieves batches of values from one multi-valued attribute. A collector can finish paging its group search while still failing to assemble the membership values of an individual group. Inspect returned attribute names and completion of ranges when diagnosing a raw LDAP export.
The primaryGroupID schema reference defines the account attribute as the relative identifier of its primary group. A group-only export does not examine that account attribute. Our article on primaryGroupID membership missed by member-only exports explains that separate membership path. Use it to understand the coverage boundary; this review does not require changing primaryGroupID or reproducing the abuse technique.
Before interpreting a blank result, write down the exact DC, domain, search base, collection identity and relevant limits. Keep the returned errors. If those facts cannot be established, mark membership as unknown and assign a follow-up owner. Do not turn an operational failure into a successful empty result.
Detection: Verify the Group on a Chosen DC
The following PowerShell examples use syntax documented by Microsoft Learn. They are read-only review examples adapted with placeholder environment values, not commands executed against an AD lab for this article. Replace the server, domain and group identity before running them from a system with the ActiveDirectory module available.
Step 1: Establish identity before counting members
Use a verified DN, GUID or SID rather than assuming the display name uniquely identifies the intended privileged group. Get-ADGroup supports these identities and explicit property retrieval:
$server = 'dc01.example.com'
$groupIdentity = 'CN=Schema Admins,CN=Users,DC=example,DC=com'
$group = Get-ADGroup -Identity $groupIdentity -Server $server -Properties member -ErrorAction Stop
$group | Select-Object DistinguishedName, SID, GroupScope, GroupCategory, member
For this example, select a DC in the forest root domain containing Schema Admins and replace the DN accordingly. Record the SID returned and reconcile it with the authoritative group inventory. If reviewing an operator group instead, choose its actual identity and domain. Preserve the identity record even when the membership read is empty.
Step 2: Keep direct and recursive results separately
Get-ADGroupMember accepts an explicit server and group identity. Its recursive mode expands the hierarchy to members without child objects. Retain the non-recursive result to see group objects that a flattened result does not preserve:
$direct = @(Get-ADGroupMember -Identity $group.DistinguishedName -Server $server -ErrorAction Stop)
$direct | Select-Object DistinguishedName, objectClass, SID
$recursive = @(Get-ADGroupMember -Identity $group.DistinguishedName -Server $server -Recursive -ErrorAction Stop)
$recursive | Select-Object DistinguishedName, objectClass, SID
Treat these as separate evidence sets, not interchangeable counts. If a direct result contains a group, review that group's identity and membership on the appropriate DC. An empty nested group is still a direct member of its parent. A recursive output without accounts should not erase that relationship from the review record.
-ErrorAction Stop displays an error and stops the command; it escalates non-terminating errors to terminating errors. If a command fails, stop that measurement and retain the error; do not reuse a previous variable's contents as the new result. A successful command remains evidence for its chosen server and readable scope, not an unconditional certification of every domain or trust. Microsoft's Get-ADGroupMember documentation also excludes Active Directory snapshots and groups with members in another forest when that forest lacks Active Directory Web Service. Record either limitation as an unresolved read, not an empty group.
Step 3: Read primaryGroupID where it is relevant
For a verified domain security group, an account-side check can compare primaryGroupID with that group's RID. Keep the query within the domain represented by the group SID: the RID alone is not a forest-wide identity. Do not mechanically apply this domain-group example to a Builtin-container SID.
The following uses the documented LDAPFilter, SearchBase, Server and Properties parameters of Get-ADObject, combined with the schema's definition of primaryGroupID:
$domainBase = 'DC=example,DC=com'
$rid = $group.SID.Value.Split('-')[-1]
$primaryMembers = @(Get-ADObject -LDAPFilter "(primaryGroupID=$rid)" -SearchBase $domainBase -SearchScope Subtree -Server $server -Properties primaryGroupID,objectSid -ErrorAction Stop)
$primaryMembers | Select-Object DistinguishedName, objectClass, objectSid, primaryGroupID
This is a review query, not a primary-group remediation script. Establish its applicability and readable population before interpreting the output. For nested domain groups, consider the relevant account-side evidence at those levels as well; do not assume the top-level recursive result answers every primary-group question. If the population or attribute visibility is incomplete, retain that limitation in the conclusion.
Remediation: Resolve the Evidence Gap Before Closure
Remediation here means correcting the review's uncertainty or documenting the intended configuration. Membership changes require a separate justification. Use the following sequence after collecting the identity and membership evidence.
- Resolve unknown reads. Investigate the recorded error, collection identity, attribute visibility, search scope and object limits. Where raw LDAP returns ranged membership attributes, use a range-aware retrieval path and retain its completion evidence. Rerun the relevant read on the recorded DC after the collection problem is resolved.
- Reconcile confirmed members. If native review returns members while the collector reports an empty list, preserve both outputs with their collection times and identities. Investigate the difference before classifying the finding. Do not remove valid members to force agreement with an incomplete export.
- Document intentional emptiness. Ask the group owner to confirm the operating purpose, approved empty state and process for any future use. Attach the verified identity and read scope to that decision. For an operator group with no default members, document why the current state fits the local administration model.
- Escalate unexpected emptiness. If the owner expected active administrators or a documented nested group, investigate the discrepancy through the organization's change process. Do not automatically repopulate the group from an old export or add a broad identity as a quick workaround.
- Retain the closure record. Include the owner, decision, evidence date, DC, group SID, commands, errors and unresolved boundaries. Define when the decision must be revisited, such as a change to the administration model or a new mismatch in collected evidence.
The related guide on dangerous over-membership in privileged groups and intentional Schema Admins emptiness explains why filling a privileged group is not an appropriate response to this Info. Keep its access-risk discussion separate from the evidence-completeness decision here.
For example, a reviewer might receive an empty collector list for an operator group. If the identity matches, the independent reads complete within the required scope, and the owner confirms that no operators are intended, the decision can preserve the empty state. If the same review encounters an unresolved read error, the correct outcome is unknown membership with a named follow-up. These are illustrative decisions, not observed test results.
How EtcSec Detects This
EtcSec's GROUP_EMPTY_PRIVILEGED detector produces an Info finding for recognized privileged groups whose collected Member list satisfies len(Member) == 0. The reviewed etc-collector revision is 20268a321609c193b79c5da6764f3f56950449cb; the detector is public/internal/audit/detectors/ad/groups/privileged/empty-privileged.go.
It recognizes ten English names: Domain Admins, Enterprise Admins, Schema Admins, Administrators, Account Operators, Server Operators, Backup Operators, Print Operators, DnsAdmins and Group Policy Creator Owners. It compares sAMAccountName case-insensitively, falling back to DisplayName when sAMAccountName is empty, or matches a cn= name substring anywhere in the DN. It does not verify a SID or an RDN boundary. Renamed or localized groups can be missed; a name match alone does not establish built-in identity.
The predicate performs no nested membership expansion and no primaryGroupID calculation. In the reviewed LDAP provider, group collection defaults to (objectClass=group) within the configured BaseDN and object limit. The parser reads exactly member, with no assembly of member;range=... values. An absent exact attribute becomes an empty list. Object pagination does not correct that attribute-retrieval gap.
These code observations establish a risk of incomplete interpretation, not a demonstrated false positive in a live AD environment. No AD tests were executed for this article. The detector's generic recommendation mentions removing unused groups; it must not be applied to built-in groups. Use the Info to trigger the identity, completeness and owner review above.
Explore the identity security pages that support this topic
