Some time ago Microsoft released a very cool feature that caught our attention. That was a passwordless authentication functionality that provides seamless single sign-on (SSO) to on-premises resources, using security keys such as the famous FIDO2 keys.
So, the idea was simple, you could sign-in to your hybrid Azure AD-joined Windows 10 device and automatically access both cloud and on-premises resources. The FIDO2 security key became the access key to the two kingdoms. SSO everywhere, and no passwords needed. The holy grail.
Microsoft had previously released the same functionality only for Azure AD-joined devices, but now the scope has been expanded to Hybrid environments. How did they do it? How does it work? Kerberos are you there?
What we found was even better: a new credential gathering attack vector involving Read Only Domain Controllers servers! Let’s take a look at all the way from researching new functionality to implementing a new attack on Impacket.
The passwordless experience in Azure
Let’s start from the beginning. As I mentioned before, Microsoft expanded the passwordless experience to on-premises resources with Azure Active Directory. But what does it mean? At first glance, since we’re talking about SSO capabilities to on-premises resources, we should also talk about Kerberos.
Kerberos is the main authentication protocol that is used to verify the identity of a security principal (a user or a host) in a Windows domain. It’s based on symmetric cryptography (shared secrets) and uses the concept of tickets to validates those identities. Roughly speaking, Kerberos issues two kind of tickets, a Ticket Granting Ticket (TGT) that validates the identity of a principal, and a Service Ticket used by the principal to authenticates against other services in the domain.
Let’s suppose I want to access to Service 1 that runs in the Server A. An authentication flow would be as follows:
Pretty clear, isn’t it? Well, let’s make things a little more complicated. Let’s move this situation to the hybrid world. An authentication flow with security keys would be as follows:
So, what do we have here? Partial TGTs trading for fully ones, and Kerberos Server keys replicated in the cloud. What is all this? Let’s start to dig into this.
Up to now, only the Key Distribution Center (KDC) service, located on the Domain Controller, had the authority to issue TGTs. However, the introduction of this new passwordless experience changed things a bit: as we could see in the previous flow, Azure AD can now issue Kerberos TGTs for one or more domains! This brought to my mind another question, what about the krbtgt account?
The security principal used by the KDC in any domain is the krbtgt account. This is a special account that can’t be deleted, nor can the name be changed, and its password is used to derive a cryptographic key for encrypting the TGTs that the KDC issues. For obvious reasons, this account doesn’t leave the AD server. So, how can Azure AD issue TGTs without this special account? This is where the figure of the Azure AD Kerberos server object appears.
An Azure AD Kerberos Server is an object created in the on-premises AD that is replicated in Azure AD and not associated with any physical servers (it’s virtual). It’s composed by a disabled user account object that follows the naming format “krbtgt_######” and an associated computer account “AzureADKerberos”. Doesn’t this sound familiar?
This object is used by Azure AD to generate the partial TGTs for the domain. How? Well, the user account holds the Azure AD Kerberos Server TGT encryption key. This is the Kerberos Server key used in the second step of the authentication flow to encrypt the partial ticket.
Half the problem solved. The krbtgt key of our domain doesn’t need to be published to the cloud, instead of that, the key of this new krbtgt_###### account will be used. So, Azure can now issue tickets but, what about Service Tickets and Authorization?
Service Tickets and authorization continue to be controlled by on-premises AD domain controllers. Azure AD doesn’t have all needed information to include the Privileged Attribute Certificate (PAC) into the tickets, that’s the reason why it only issues partial tickets.
How does the flow continue? Once we obtain the partial ticket, we have to trade it (against an on-prem AD) for a fully one that will include all the authorization information needed, and finally, use it to request the different Service Tickets to access the on-premises resources. With that, we obtain the Kerberos SSO capabilities and the passwordless experience is completed.
At this point, just one question remained, what is this Kerberos Server object really? (Why our on-premises AD trust in its the key?) And the answer was very easy to obtain just by seeing the properties of the machine account related to the object:
We’re talking about a Read-Only Domain Controller (RODC). Microsoft reuses the concept of RODC to implement a “cloud” version of Kerberos that allows Azure AD to offer SSO capabilities. I told you that there was something that sounded familiar to me.
Remember Read Only Domain Controllers?
Before continuing, it’s important to review some basic concepts about RODC. If anyone wants to learn more about it, I totally recommend this great article from Sean Metcalf.
In a few words, a RODC is a type of domain controller that hosts read-only partitions of the Active Directory database. Except for account passwords, it holds all the AD objects and attributes that a writable domain controller holds. However, changes cannot be made to the database that is stored on the RODC. It’s designed primarily to be deployed in remote or branch office environments, which typically have relatively few users, poor physical security, or poor network bandwidth to a hub site.
The main concept that I want to review, and that will be very useful in what comes, is the credential caching. As I mentioned before, the account passwords aren’t saved into the RODC by default (for security purposes), so the authentication process in a branch site is a little bit different:
- First, the user sends a TGT request to the RODC of their site.
- The RODC server checks if the user credential is cached:
- If not, the RODC forwards the request to a writable DC (continues to 3)
- If the credentials are cached, the authentication is resolved by the RODC (finishes here)
- The writable DC authenticates the user, signs a TGT, and sends the response back to the RODC.
- The RODC will check if the user if allowed to cache their credentials:
- If the user is included in the Allowed RODC Password Replication, their credentials are stored in the server, and the msDS-RevealedList attribute of the RODC is populated with the username. Subsequent authentication requests will be managed by the RODC.
- If the user is included in the Denied RODC Password Replication, their credentials won’t be stored, and subsequent authentication requests will be forwarded to a writable DC.
- Finally, the RODC forwards the TGT to the user which can use it to request Service Tickets.
So, the caching is useful to ensure users and computers can authenticate to RODC when a writable DC is inaccessible and RODC can’t forward requests to it. However, this could be a double-edged sword. The reason why there are no cached credentials is to prevent the entire domain from being put at risk when a RODC server is compromised. As we saw, these branch sites have a lower level of security. So, the main idea behind the credential cache is just to keep the minimum number of passwords needed to operate on the site. Let’s keep this in mind for the future.
Legacy protocols for the win
Going back to the passwordless scenario, we saw how Microsoft supports SSO to on-premises resources in hybrid environments using Kerberos. However, what happened with the access to resources that use legacy protocols like NTLM? Yes, we can’t easily get rid of legacy protocols.
The easy way to start analyzing this situation was inspect a Wireshark capture of a passwordless authentication. The part of the authentication that we were most interested in analyzing was steps 4 and 5 of Figure 2, the exchange between partial and full tickets.
The fully TGT is obtained via a TGS-REQ (packet n°577) to the KDC (krbtgt service):
The TGS-REQ included two pre-authentication data (PA-DATA). The PA-TGS-REQ with the partial TGT and an unknown PA-DATA type number 161.
An unknown type is a clear sign that something is happening there. If Wireshark didn’t have that data type defined, it’s because that data is relatively new. So, the first thing to do was to review the [MS-KILE]: Kerberos Protocol Extensions, and check this PA-DATA type. First result to come was a new type of TGS-REQ:
126.96.36.199.8 Key List Request
When a Key Distribution Center (KDC) receives a TGS-REQ message for the krbtgt service name (sname) containing a KERB-KEY-LIST-REQ  padata type the KDC SHOULD include the long-term secrets of the client for the requested encryption types in the KERB-KEY-LIST-REP  response message and insert it into the encrypted-pa-data of the EncKDCRepPart structure, as defined in [RFC6806].
The mention of long-term secrets made me think I was on the right track. And it was! Bingo!
The KERB-KEY-LIST-REQ structure is used to request a list of key types the KDC can supply to the client to support single sign-on capabilities in legacy protocols. Its structure is defined using ASN.1 notation. The syntax is as follows:
KERB-KEY-LIST-REQ ::= SEQUENCE OF Int32 — encryption type —
The structure of the KERB-KEY-LIST-REQ is used to support SSO capabilities with legacy protocols. It only remained to check what type of encryption was requested and what was the response. Going back to the capture:
The content of the PA-DATA was the encoded value of 3003020117, that represented the encryption type 23 or RC4-HMAC. Yes, we were requesting the user’s NT Hash!
After confirmed that, I started reviewing the response (packet n°583):
Inside the encrypted part of the TGS-REP (decrypted with the session key) we could find the PA-DATA type 162, the KERB-KEY-LIST-REP:
Going back to the MS-KILE, I checked the encoding of the structure to decode data and get the key:
The KERB-KEY-LIST-REP structure contains a list of key types the KDC has supplied to the client to support single sign-on capabilities in legacy protocols. Its structure is defined using ASN.1 notation. The syntax is as follows:
KERB-KEY-LIST-REP ::= SEQUENCE OF EncryptionKey
The encoded PA-DATA was decoded into:
The user is now able to use its NT-Hash to authenticate with NTLM. SSO in legacy protocols!
The Key List attack
Bingo! We found the way Windows implements SSO with legacy protocols. After checking that, the team’s reaction was immediate, we also found a new potential way to dump credentials with lower requirements!
The idea behind this new technique is simple. If we were able to reproduce the previous TGS-REQ with the new PA-DATA, we would have the list of all long-term secrets of the user.
So, the first attempt was to replicate the TGS-REQ to the krbtgt service adding the KERB-KEY-LIST-REQ structure with a regular user. That means that the TGT included was issued to this regular user by the KDC, something easy to obtain without needing to know the krbtgt credentials. The response was a normal TGS-REP with no new data (no KERB-KEY-LIST-REP was included). No problem let’s escalate. The second attempt was a new TGS-REQ of an administrator user. The same process as before, and the same result, no keys in the answer. The idea wasn’t so straightforward.
If the process works for RODC, let’s try including a partial TGT signed by this server into the TGS-REQ. And here we have the answer. Replicate a partial TGT signed by a RODC and issued to a particular user, include it into a TGS-REQ, decrypt the response and get the key. Repeat to whatever user we want.
After a couple of attempts, I noticed that some users obtained the error: KDC_ERR_TGT_REVOKED (TGT has been revoked). Why? Those users were included in the group Denied RODC Password Replication, and consequently, have this new Kerberos function restricted. Users such as Administrators are denied replicating their passwords by default. Here we remember the importance of managing the password replication permissions that we talked about in the RODC section.
The good news, we can attack both physical RODC servers and virtual ones (the Azure AD Kerberos Server object is included in our attack surface!). And something no less important, the targets users don’t need to be cached in the RODC! Something that was needed in previous attacks against this kind of Domain Controllers.
So, what are the requirements? In summary:
- We must know the krbtgt credentials of the RODC (-rodcKey) because we need to create a partial ticket and a TGS-REQ with some special conditions. We have several ways to get those credentials, for instance, if we obtain local admin to the RODC, we can dump the SAM database with Mimikatz. If we’re talking about virtual RODC, we can target the Azure AD Connect Server.
- We must have the ID of the krbtgt account of the RODC (-rodcNo). Not big deal.
- We must target users that aren’t explicitly detailed in Denied group.
With these requirements in place, I opened a PR that includes a new example script (keylistattack.py) and a new option (-use-keylist) in secretsdump.py to demonstrate the attack.
Basically, the attack has two main parts: the user listing and the ticket requesting:
First, we need to know what our target is. We can define the target by parameter (LIST option) defining a target username (-t flag) or defining a file with a list of target usernames (-tf flag), or we can do an enumeration (for instance, a SAMR user enumeration). For the last option, we need a low credential user, and we have two options. The default option, that filter the domain users included in the Denied group, and the full one (-full flag), that hit everybody!
Once we know who to attack, we request the ticket, process the response, and get the keys!
How to detect this ?
Presented the new attack, how can we detect it? As the attack implements a valid Key List request that could be present in the normal operation of an environment with passwordless enabled, the options aren’t many:
- Audit the enumeration operations:
- SAMR enumeration: Event 4661 – A handle to an object was requested (Object Type: SAM_DOMAIN, SAM_ALIAS, SAM_GROUP).
- LDAP enumeration
- Audit the Kerberos Service Ticket Operations:
- Success requests: Event 4769 – A Kerberos service ticket was requested (Ticket Options: 0x10000 – Proxiable)
- TGT revoked: Event 4769 – A Kerberos service ticket was requested (Failure Code: 0x14 – KDC_ERR_TGT_REVOKED)
How to mitigate this attack?
- Don’t add “Authenticated Users” or “Domain Users” to the “Allowed RODC Password Replication Group”. If it is required, those RODCs should be protected in a similar level to a writable DC (tier 0).
- Add all privileged accounts and groups to the “Denied RODC Password Replication Group”.
- Don’t set regular user accounts as RODC administrators. These kinds of accounts are usually less secure than high-profile accounts, and their compromised could lead to the credential dumping of local accounts (including the krbtgt account of the RODC).
Virtual RODCs (Azure AD Kerberos Server/Passwordless scenario):
- Make sure to add only users with passwordless capabilities to the “Allowed RODC Password Replication Group”.
- The Azure AD Connect server contains critical identity data and should be treated as a Tier 0.
- Both physical and virtual RODCs can be attacked.
- The attack surface in virtual RODCs is more extensive due to the required replication permissions.
- The accounts to attack don’t need to be cached on the RODC.
- No administrator credentials are needed, and if you have a list of users, you don’t even need credentials.
- The attack requires that there is at least one DC server with the updated versions of Windows 2016/2019 (patches kb4534307 and kb4534321 respectively).