AD-INF-014: Rogue DC candidates: DC flag without an NTDS Settings object¶
medium high Infrastructure MITRE ATT&CK T1207, T1098 Reads: LDAP
A computer carries a domain controller flag in userAccountControl but has no nTDSDSA object in the Configuration partition, so it is a rogue DC candidate.
Also known as: rogue-dc
Why it matters¶
A real domain controller has two halves: a computer account in the domain partition and,
under CN=Sites in the Configuration partition, a server object with an nTDSDSA child
(NTDS Settings). Promotion creates both.
The flag in userAccountControl is only an attribute. Anyone who can set
SERVER_TRUST_ACCOUNT on a computer gets an account that looks like a DC and normally has
Domain Controllers as its primary group. That group holds Replicating Directory Changes All
on the domain head (seen in the lab), so the flag is a quiet way to keep DCSync-level rights.
Without this check, any logic that exempts DCs would exempt that computer too: unconstrained delegation, LAPS and primary group findings all skip real DCs.
How ad-doctor detects it¶
ad-doctor reads every server and nTDSDSA object under CN=Sites, follows
serverReference from each nTDSDSA parent to its computer, and compares that set with the
computers whose userAccountControl has SERVER_TRUST_ACCOUNT or
PARTIAL_SECRETS_ACCOUNT. Read-only DCs have an nTDSDSA object as well, so they count as
registered.
A computer that fails the comparison stays a tier-0 target: control over it is still reported by AD-ACL-004 and the attack paths. It loses the DC exemptions: AD-DEL-001, AD-SEC-001, AD-ACC-010, AD-PRIV-006 and AD-PRIV-007 judge it as an ordinary computer. The probes do not contact it, so no credentials go to a host that only claims to be a DC.
The Microsoft Entra Kerberos object is left out. Microsoft describes it as logical, not a
physical server, working like a read-only DC, so it never has an nTDSDSA object. It is
matched by structure, not name alone: CN=AzureADKerberos directly under OU=Domain
Controllers, PARTIAL_SECRETS_ACCOUNT without SERVER_TRUST_ACCOUNT, and a set
msDS-KrbTgtLink. That match has been tested on made-up data only; the lab has no such object.
When no nTDSDSA object can be read, or one has no readable serverReference, nothing is
marked and the report has a note that the check was not judged: part of the set is missing, and
a real DC must not be called rogue because of it. A server object whose nTDSDSA did not come
back may only be hidden from this account, so its computer is not marked, and a note gives the
count. An older snapshot has no such data: it gets the same kind of note and
every flagged computer stays a DC, as before.
The opposite case, an nTDSDSA object with no computer behind it, is not reported: in a
multi-domain forest most of those belong to other domains.
Attributes and probes read
userAccountControl (SERVER_TRUST_ACCOUNT 0x2000, PARTIAL_SECRETS_ACCOUNT 0x4000000)server objects under CN=Sites (serverReference)nTDSDSA objects under CN=SitesmsDS-KrbTgtLink
Severity¶
High when an enabled computer has SERVER_TRUST_ACCOUNT (0x2000): Windows treats it as a
writable DC, and it normally has Domain Controllers as its primary group. Medium when the
account is disabled, or only has the read-only DC flag (PARTIAL_SECRETS_ACCOUNT).
False positives and limits¶
- A failed promotion or demotion. The computer object can keep the flag after the Configuration objects are gone. That still needs cleaning up, because the account keeps its rights.
- A restored computer object whose
serverobject was not restored. - A DC whose objects sit in another site container is still found: the whole of
CN=Sitesis searched.
Fix¶
-
Find out who set the flag and when: the replication metadata shows the last originating change.
-
If it is not a DC, make it an ordinary computer again and change its password:
-
If it is the remains of a DC, finish the metadata cleanup, then delete the computer object or reset it as above.
-
Review who can write
userAccountControlon computers and hold the right to set the flag: only tier-0 admins should.
Verify¶
To accept the risk instead, add an entry to the file passed with --accept (see accepted risks):
[[accept]]
check = "AD-INF-014"
objects = ["..."] # optional; without it the whole check is accepted
reason = "..."
expires = 2027-03-31
Related checks¶
References¶
Microsoft and vendors
- Purple Knight security indicators (Domain Controller in inconsistent state)
- Configure Microsoft Entra Kerberos trust
Specifications
MITRE ATT&CK
New in the next release.