AD-INV-001: Group membership differs from the declared state¶
high Invariants Reads: LDAP
The membership of a group declared in the invariants file differs from the declared list: someone was added to it, or a declared member is gone.
Why it matters¶
Adding an account to Domain Admins or another privileged group is the most common way to keep access after a compromise, and the most common quiet mistake ("temporarily" made admin for a migration). Generic checks report large or non-empty groups, but only the team knows that tier 0 should be exactly these people. Declaring it turns every unreviewed change into a finding on the next scan, and in CI into a failed job.
How ad-doctor detects it¶
Runs only with --invariants FILE, for each group under [groups]:
- The group is found by name, ignoring case. For the privileged groups ad-doctor already
collects (Domain, Enterprise and Schema Admins, Administrators and the four operator
groups) it uses their transitive membership from that collection, which also includes
accounts made members through
primaryGroupID. For any other group it expands nested groups from the snapshot (cycles are cut). - Members are compared by
sAMAccountName, ignoring case. Only user and computer accounts count; nested groups are expanded, not compared. - Each difference is one object:
Domain Admins: unexpected svc_sqlorDomain Admins: missing adm_alice.
A declared group that does not exist in the snapshot reports every declared member as missing.
Attributes and probes read
membersAMAccountName
Severity¶
Always high. The groups people declare are almost always tier 0, and an unexpected member of one is either a mistake or an attacker's persistence.
False positives and limits¶
- Members from another domain or forest (foreign security principals) are not resolved to accounts and are left out of the comparison.
- Membership through
primaryGroupIDis counted only for the privileged groups above; for other declared groups it is missed. See AD-PRIV-007.
Fix¶
-
Find out who made the change and when before reverting it. On a DC, group changes are logged as events 4728/4732/4756 (member added) when auditing of security group management is on:
-
If the change was not intended, revert it and treat the account as compromised until you know otherwise:
-
If it was intended, change the invariants file in a reviewed pull request, so the decision is recorded.
-
Run the check in CI so the next drift fails the build; see Invariants and CI integration.
Verify¶
ad-doctor scan dc01.corp.example --ldaps -u 'CORP\auditor' --invariants invariants.toml --only AD-INV-001
To accept the risk instead, add an entry to the file passed with --accept (see accepted risks):
[[accept]]
check = "AD-INV-001"
objects = ["..."] # optional; without it the whole check is accepted
reason = "..."
expires = 2027-03-31
Related checks¶
References¶
Microsoft and vendors
- Appendix B: Privileged accounts and groups in Active Directory
- Monitoring Active Directory for signs of compromise
Available since 0.1.0.