The Problem

Lets start from the top and look at the problem it solved The problem it solves: before RODC existed (was introduced in 2008), if a branch office needed domain authentication they had two options to provide that

  • Deploy full writable DC at the branch
    • you get full copy of AD database including every password hash in the domain which provides you with fast local authentication
    • on the other hand you'd need a physical security at that branch like security guards, cage, physical access, cameras, etc. and if you can't provide that it would be disaster if you left the DC just hanging there
  • No DC at the branch
    • all authentication goes over WAN which is slower and less reliable if the WAN link drops

because those 2 options weren't good enough, the RODC was built

  • give the branch office local authentication without putting the full domain database at risk

so What is RODC ?

  • Read-Only Domain Controller is a special DC role that holds a read-only, filtered replica of the Active Directory designed for physically insecure branches

the Read-Only Solution

having RODC in the first place was introduced to separate full writable DCs from RODCs that's why RODCs provide the Following

  1. the AD Database is read only
    • the RODC holds a copy of the ntds.dit but the AD replication manager marks it as non-originating replica meaning it can only receive changes from a writable DC but it can't produce changes on its own
    • if a client tries to do something that requires writes like writing an object, changing a password, or modifying an attribute the RODC either rejects it directly or proxies the request to the writable DC where the write will happen then changes eventually replicated back at the RODC
  2. the RODC database if Filtered
    • if we replicated the entire DC and marked it as RODC there is still some security risks like having the main DC krbtgt hash stolen or something that's why the database is partial not full database
    • and the rule is simple → the RODC has all AD objects (users, computers, groups, GPOs, OUs) but the credentials and the Kerberos keys is stripped out unless they are explicitly permitted by the PRP (Password Replication Policy)
  3. DNS and SYSVOL are read-only
    • If the RODC runs DNS, it hosts a read-only copy of the DNS zone, Client dynamic DNS updates get proxied to a writable DC
    • SYSVOL modification stays on the RODC and is not replicated out
  4. Administrator Role Separation
    • RODC Administration can be delegated to a domain user account without compromising the security on the active directory and it provides more of its own features but we'll get to the details later on

RODC's own krbtgt Account

lets take a step back and ask why does a KRBTGT account exist at all ? if you looked at my article about Kerberoasting here you would see a full Kerberos authentication process but what i care about is the AS-REP message Pasted image 20260409132529.png you can see in this AS-REP that the TGT which is opaque to the client is encrypted using the krbtgt hash in every AD domain there is one special account called krbtgt and It exists for that purpose: its password hash is the secret key used to encrypt and sign every TGT (Ticket Granting Ticket) in the domain this part is opaque to you so you can't read what's inside it you just carry that TGT and present it around when you need a service ticket and where do you get a TGS ? the KDC at the Domain Controller and guess what he is the only one who can read this TGT because the is only one who has the krbtgt encryption key so he can decrypt it and decide what's gonna happen next ? either to issue a TGS or not

the issue with Kerberos tickets that they can be forged and the only reason they are issued at the KDC is because this is supposed to be the most safe place for that krbtgt hash otherwise each machine can forge a ticket for itself if it has this hash

so it isn't safe for the krbtgt hash to be placed at RODC and that's one of the features RODC provides → its own krbtgt account when an RODC is promoted, AD automatically creates a new account with the naming convention krbtgt_XXXXXwhere that XXXXX number is derived from the RODC's msDS-SecondaryKrbTgtNumber attribute lets say this account is krbtgt_12345 so this account:

  • lives in CN=Users,DC=corp,DC=local just like the main krbtgt
  • has its own independent password and hash
  • owned and managed by that specific RODC
  • has msDS-KrbTgtLinkBl attribute linking it back to the RODC computer object
  • cannot be used to issue tickets on any other DC legitimately

just keep this in your mind, all the pieces will be puzzled together soon

Password Replication Policy PRP

we mentioned earlier that the RODC holds a partial database and there is not credentials stored there by default except the RODC computer account the the RODC KRBTGT account now some other accounts' credentials can be stored but they have to be added explicitly (much safer this way)

PRP is the mechanism that answers the question which accounts are allowed to have their credentials stored on this specific RODC? every RODC has its own PRP and it works through two lists on each RODC Computer Object in AD (note that it is set on the RODC computer object)

  1. msDS-RevealOnDemandGroup attribute which is like the allow list
    • accounts or groups listed here may have their credentials stored on this RODC
    • and I say may because being on this list doesn't mean the account is cached yet only it is permitted to
  2. msDS-NeverRevealGroup attribute which is the deny list
    • accounts or groups listed here will never be cached on this RODC
    • the deny always wins over allow meaning that if an account appears in both, deny takes priority
    • this list has the next groups and accounts by default
plaintext
Domain Admins
Enterprise Admins
Schema Admins
Group Policy Creator Owners
Domain Controllers (the RWDC group)
krbtgt (domain-wide)
Denied RODC Password Replication Group

the third attribute that is related to this PRP is msDS-RevealedList which is a read-only attribute maintained by the RWDC and it records every account whose credentials have been successfully sent to the RODC

  1. accounts in the msDS-RevealOnDemandGroup and not in msDS-RevealedList are allowed to cache but not yet cached
  2. an account in the msDS-RevealedList → the hash sits in the ntds.dit at the RODC right now

and this is the entire caching flow ![[diagram-export-4-9-2026-2_10_09-PM.png]]

Ticket Requesting

now with this being said we have all the puzzle pieces, how does the actual ticket requesting happen ? we already now how does it work in a normal DC situation but what if there is a RODC in place, are there any special flags in the TGT ?

Before the flow, lock these three things in your head:

  • RODC only holds credentials for accounts in msDS-RevealedList
  • RODC signs tickets with krbtgt_XXXXX which is its own key, not the domain KRBTGT
  • RWDC is the checkpoint that validates everything the RODC issues

we need to know what inside TGT? here the unencrypted header which is visible to anyone:

  • client name
  • realm (domain name)
  • KVNO (Key Version Number) and it identifies which key was used to encrypt the ticket

the encrypted body only the KDC can read this (the box in purple)

  • session key (used for future communication between client and KDC)
  • auth time, start time, end time, renew-till
  • ticket flags (forwardable, renewable, and more)
  • client IP (optional)
  • the PAC Privilege Attribute Certificate

the PAC is the most important part it contains

  • username
  • user RID
  • group memberships and their SIDs
  • logon time, password expiry
  • Domain SID
  • two signatures which is server signature and KDC signature

this PAC is the part that tells service what are your permissions and the entire authorization system depends on it

TGT issued by the RODC contains a special marker in its structure inside the encrypted part of the ticket there is a field called the KERB-AD-RESTRICTION-ENTRY and the KVNO (Key Version Number) in the ticket header identifies which key was used to sign it

RODC local ticket issuance

the flow starts by client sending normal AS-REQ then the RODC checks if this user is cached in the ntds.dit before even validating the pre-auth if the user is cached then there is no need to proxy to RWDC and it starts by decrypting the timestamp in the AS-REQ if this succeeded then the RODC starts constructing the PAC of that client

plaintext
PAC contains:
  Username: jsmith
  User RID: 1234
  Group SIDs: Domain Users, BranchUsers, etc.
  Logon time: now
  Domain SID: S-1-5-21-XXXXXXX

and we know RODC has all the AD objects replicated RODC starts signing the PAC and we know the PAC needs two

plaintext
Server signature: HMAC-MD5 signed with krbtgt_12345 key
KDC signature:    HMAC-MD5 signed with krbtgt_12345 key

then the RODC starts building the TGT and encrypting it

  • generate fresh random session key
  • construct full TGT body
  • encrypt the entire TGT body with the krbtgt_12345 aes key
  • sets the KVNO header to 12345

the KVNO header is critical (it isn't encrypted) it is just used to tell every DC that this was signed and encrypted but that KRBTGT version number look up who owns it and then finally the RODC sends the AS-REP back to the client and the process of the TGS keeps going

now the question arises ? (maybe only in my head but it is a question) if we can sign a ticket with the krbtgt_12345 and just use it, so what was the point of having it in the first place i mean i can get a ticket for RWDC domain admin using that key and it kinda acts like the main DC krbtgt so it would be valid ?

no it doesn't work that way cause there is a rule and it actually makes so much sense A TGT generated by an RODC can be used in TGS-REQs to obtain service tickets from the same RODC or from writable Domain Controllers. When a TGT generated by an RODC is presented to a writable Domain Controller, the Domain Controller only accepts it if the ticket was generated for a principal listed in the RODC’s msDS-RevealOnDemandGroup attribute and not listed in the RODC’s msDS-NeverRevealGroup attribute.

How does it make sense? think of it this way to get a TGT issued for a certain user by RODC, this user must be cached in RODC and it can't be cached unless it satisfies those two conditions so when you present a fake TGT to RWDC says you got a TGT for Domain Administrator which doesn't satisfy those conditions by default it would ask how would you get a TGT for domain admin from RODC if this admin hash was never cached there and this is the analogy of validation

now the last part what can we do as attackers if we got access on RODC? we can do next

  1. RODC golden ticket
  2. Key list attack

RODC golden ticket

now that you got all pieces if you wanna a better understanding for this try out HTB Garfield machine or checkout my writeup about it once it is published here to get a golden

  • You can forge an RODC golden ticket and present it to a writable Domain Controller only for principals listed in the RODC’s msDS-RevealOnDemandGroup attribute and not in the RODC’s msDS-NeverRevealGroup attribute
  • When the RODC golden ticket is presented to a writable Domain Controller, the writable DC will regenerate the PAC in the resulting ticket rather than copying from the presented TGT

what we need to do this attack

  1. NT/SYSTEM on RODC to dump ntds.dit and get the krbtgt_xxxxx AES key
  2. KVNO number
  3. Domain SID
  4. Principal in msDS-RevealOnDemandGroup and not in msDS-NeverRevealGroup
  5. User RID

and then using Rubeus we can get that ticket

powershell
Rubeus.exe golden /rodcNumber:$KBRTGT_NUMBER /flags:forwardable,renewable,enc_pa_rep /nowrap /outfile:ticket.kirbi /aes256:$KRBTGT_AES_KEY /user:USER /id:USER_RID /domain:domain.local /sid:DOMAIN_SID

the issue with this attack isn't the Forging itself but how are we gonna get there so we need to find an account who have write access over those attributes and a way to get NT/SYSTEM on RODC usually check RODC Administrators group members for Write Access over those attributes and to get NT/SYSTEM look at the RODC$ machine account and find a way to abuse it

Key List attack

It is possible to retrieve the long term secret of a user by sending a TGS-REQ (service ticket request) to the KRBTGT service with a KERB-KEY-LIST-REQ message type. This was introduced initially to support SSO with legacy protocols with Azure AD on on-premises resources.

just to keep it simple Azure AD has it own new way of Authentication but there is some resources that still use the old way NTLM hashes now this AzureAD kerberos server is just RODC as Leonardo joked about it in this slides Pasted image 20260409152119.png

An attacker can abuse this by forging a RODC golden ticket for a target user and use it to send a TGS-REQ to the KRBTGT service with a padata filed value of 161 (KERB-KEY-LIST-REQ). Knowing the KRBTGT key of the RODC is required here. The TGS-REP will contain the long term secret of the user in the KERB-KEY-LIST-REP key value.

so what we need is

  1. low privilege access
  2. RODC KRBTGT number
  3. RODC KRBTGT AES key
  4. the virtual RODC and the physical RODC must not belong to the denied group

and then we can use impacket-keylistattack

shell
keylistattack.py -rodcNo "$KBRTGT_NUMBER" -rodcKey "$KRBTGT_AES_KEY" -full "$DOMAIN" /"$USER":"$PASSWORD"@"$RODC-server"

or dump specific user's hash

shell
keylistattack.py -rodcNo "$KBRTGT_NUMBER" -rodcKey "$KRBTGT_AES_KEY" -t "$TARGETUSER" -kdc "$RODC_FQDN" LIST

or if you have a shell get a golden ticket first using the RODC golden ticket technique then dump the hashes

powershell
Rubeus.exe asktgs /enctype:aes256 /keyList /ticket:ticket.kirbi /service:krbtgt/domain.local

Mitigation and Detection

in Detection:

  • Monitor Event ID 4769 TGS-REQ at RWDC where TGT KVNO maps to krbtgt_XXXXX but the principal isn't in msDS-RevealOnDemandGroup
  • Monitor Event ID 4656/4663 object access on ntds.dit or SYSTEM hive on the RODC
  • Monitor Event ID 4688 suspicious process creation on the RODC (Mimikatz, DSInternals)
  • Audit msDS-RevealedList for unexpected high-value accounts appearing cached
  • Monitor Event ID 4769 TGS-REQ targeting the krbtgt SPN directly (rare in normal environments)
  • Monitor for padata type 161 (KERB-KEY-LIST-REQ) in Kerberos traffic via NDR/IDS
  • Enable Microsoft Defender for Identity "Suspected Key List attack" alert

in Mitigation:

  • Apply LAPS to the RODC local admin and restrict who's in the RODC Administrators group
  • Enable Credential Guard on the RODC
  • Lock write permissions on msDS-RevealOnDemandGroup and msDS-NeverRevealGroup
  • Keep msDS-RevealOnDemandGroup minimal branch users and computers only, never privileged accounts
  • On suspected compromise, rotate krbtgt_XXXXX twice with replication delay between rotations
  • If not using Azure AD Kerberos block or alert on padata type 161 unconditionally
  • Ensure krbtgt_AzureAD stays in the deny list of all physical RODCs
  • Rotate krbtgt_AzureAD periodically
  • Restrict outbound port 88 from RODC to designated hub DCs only
  • No privileged interactive logins on the RODC treat it as Tier 1

this mitigation techniques can vary from environment to another so just try to do the best you can

Resources