ADTRADE-004: RODC Password Replication Policy Hygiene

Platform
Active Directory
Category
AD Adversary Tradecraft Indicators
Severity
High
Zero Trust pillar
Visibility & Analytics (weight 2)
Golden fixtures
3
Branch coverage
Observed: fixtures prove the verdicts they exercise
Provenance
baseline

What it checks

The check enumerates every Read-Only Domain Controller object in the domain (Get-ADDomainController -Filter {IsReadOnly -eq $true}) and, for each one, reads the two attributes that define its Password Replication Policy: - msDS-RevealOnDemandGroup (the "Allow" list): principals whose secrets the RODC is permitted to cache and serve from local storage. - msDS-NeverRevealGroup (the "Deny" list): principals whose secrets the RODC is forbidden to cache, regardless of any Allow entry. Deny always wins. For each RODC the check expands both lists recursively, resolving nested groups, foreign-security-principals, and SID-history references, and produces three signals: 1. Does the Deny list still contain the built-in Denied RODC Password Replication Group (S-1-5-21-<domain>-572)? This group nests Domain Admins, Enterprise Admins, Schema Admins, Account Operators, Backup Operators, Server Operators, Print Operators, Read-Only Domain Controllers, Group Policy Creator Owners, Cert Publishers, Domain Controllers, and krbtgt by default. 2. Has any high-privileged principal (a Tier-0 group, a member of a Tier-0 group, or krbtgt) been added directly or transitively to the Allow list? 3. Has the Allow list been broadened beyond the principals that actually authenticate against the branch (typical signals: Domain Users, Authenticated Users, Everyone, Allowed RODC Password Replication Group with custom nesting). The check also reads msDS-RevealedList and msDS-AuthenticatedToAccountList on each RODC to surface principals whose secrets have already been cached, since a cached Tier-0 secret on a branch RODC is an active incident and not a policy issue.

Why it matters

The RODC was designed to put a Domain Controller in physically untrusted locations (branch offices, retail, OT enclaves) without putting the entire domain at risk. The security contract that makes that safe is the PRP: the RODC only ever holds password material for the small set of users and computers that actually authenticate at that site, and never for the privileged principals that run the forest. Break that contract and the risk model collapses. An attacker who steals or physically accesses the RODC chassis can extract the local ntds.dit and the local krbtgt account for that RODC, then offline-crack or pass-the-hash any secret the RODC ever cached. If Domain Admins, Enterprise Admins, or the real krbtgt is reachable on the Allow side, the attacker can request that those credentials be replicated to the RODC (legitimately, via prepopulation, or by triggering authentication and waiting for caching), recover them from the stolen disk, and pivot to full-domain DCSync and Golden Ticket forgery. The compromise does not require any exploit: it is a documented, supported feature of how the RODC caches credentials when the PRP allows it. Mandiant, SpecterOps, and Microsoft IR have all published on real-world cases where an RODC at a remote site, customized with a permissive PRP for "operational convenience," became the entry point for full-forest takeover.

Attack path

1. Reconnaissance: from any authenticated context the attacker enumerates RODCs (Get-ADDomainController -Filter {IsReadOnly -eq $true}) and reads their msDS-RevealOnDemandGroup and msDS-NeverRevealGroup attributes. Both are readable by Authenticated Users. 2. PRP analysis: the attacker expands the Allow list recursively to find any principal that is, or nests, a Tier-0 group. A common finding is that an administrator added "Domain Users" or a wide operations group to the Allow list to stop branch helpdesk tickets, inadvertently making every user (including DAs who occasionally log on at the branch) eligible for caching. 3. Cache priming: the attacker uses repadmin /prp add <RODC> "<target>" Allowed if they already hold sufficient rights, or simply waits for the target principal to authenticate at the branch. They can also force caching by triggering authentication: an SMB connection from the branch site, a scheduled task on a branch workstation that runs as the target, or an RDP attempt that hits the RODC for KDC service. 4. Verification: the attacker reads msDS-RevealedList on the RODC to confirm the target secret is now cached locally. 5. Physical or remote compromise of the RODC: extraction of ntds.dit and SYSTEM hive (offline if disk is stolen, online via Volume Shadow Copy and ntdsutil ifm, or via DSRM credential reuse). Tools: secretsdump.py -ntds ntds.dit -system SYSTEM LOCAL, mimikatz lsadump::dcsync against the RODC, or dcsync of the RODC krbtgt to forge tickets for principals whose secrets the RODC holds. 6. Privilege escalation: with the cached Domain Admin or krbtgt secret in hand, the attacker authenticates against a writable DC and runs full DCSync against the real krbtgt, forges a Golden Ticket, and persists across the forest. 7. Cleanup: the attacker can remove themselves from the RevealedList by triggering a prp delete, or simply rely on the RODC being remote and unmonitored.

How Guerrilla assesses it

Guerrilla binds to a writable DC and queries the Configuration partition for nTDSDSA objects whose options flag indicates RODC (NTDSDSA_OPT_IS_GC is independent; the RODC marker is on the parent server object and on the computer object via the PARTIAL_SECRETS_ACCOUNT userAccountControl flag, 0x04000000). For each RODC computer object the check reads msDS-RevealOnDemandGroup, msDS-NeverRevealGroup, msDS-RevealedList, and msDS-AuthenticatedToAccountList in a single LDAP call. Group references are resolved against the global catalog and expanded recursively in PowerShell so that nested groups, foreign-security-principals, and the built-in Allowed/Denied RODC Password Replication Groups (S-1-5-21-<domain>-571 and S-1-5-21-<domain>-572) are walked rather than treated as opaque SIDs. The check cross-references the resolved Allow membership against the Tier-0 inventory produced by ADPRIV-001 / ADPRIV-002 (Domain Admins, Enterprise Admins, Schema Admins, Account Operators, Backup Operators, the real krbtgt) and emits a finding for every overlap, recording the exact nesting path that brought the principal onto the Allow side. If the domain has no RODCs the check returns N/A and PASS. All queries are LDAP-only and read-only.

Recommended value

All RODCs in the domain have a Password Replication Policy where Domain Admins, Enterprise Admins, Schema Admins, krbtgt, and Account Operators are members of the Deny side. No high-privileged accounts are members of the Allow side.

Remediation

For each RODC: Get-ADDomainController -Filter {IsReadOnly -eq $true} | ForEach-Object { Get-ADDomainControllerPasswordReplicationPolicy -Identity $_ -Allowed; Get-ADDomainControllerPasswordReplicationPolicy -Identity $_ -Denied }. Verify the Denied list contains the 'Denied RODC Password Replication Group' built-in. If your environment has no RODCs this check is N/A — PASS. Microsoft's RODC planning guide has the canonical PRP template.

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 ADTRADE-004
ScenarioExpected verdict
cleanPASS
known-badWARN
throttledNot Assessed

Framework mappings

NIST SP 800-53
AC-6
CIS AD Benchmark
10.4.1
MITRE ATT&CK
T1003.001