On September 13, 2022, a new Kerberos vulnerability was published on the Microsoft Security Response Center’s security site. It’s labeled as a Windows Kerberos Elevation of Privilege vulnerability and given the CVE ID CVE-2022-33679. The MSRC page acknowledges James Forshaw of Google Project Zero for the disclosure and James published a detailed technical write-up of the vulnerability on Project Zero’s blog. The attack targets Windows domain accounts that have pre-authentication disabled and it attempts an encryption downgrade attack. A proof-of-concept (PoC) script was released by Bdenneu on Github that performs the attack and when successful, obtains a ticket-granting-service service ticket. In this post, we’re going to cover some high level Kerberos details, show how to exploit the vulnerability with the PoC and reveal how to extend this attack all the way to unauthenticated Kerberoasting.
In order to understand the vulnerability and what we’re doing later in this post, it’s useful to know what Kerberos is and how it works. It is also important to understand how existing attacks like Kerberoasting and AS-REP Roasting work. If you’re already familiar Kerberos and common attacks, feel free to skip this section.
Kerberos is an authentication protocol that is used to verify the identity of a user or host. The Windows Server operating systems implement Kerberos version 5 for public key authentication, transporting authorization data and delegation. It is commonly used in Windows Active Directory domains and provides the following benefits:
I’m going to focus on the Windows implementation of Kerberos 5 and how it’s used in an Active Directory domain. The following describes some of the key components and services:
Key Distribution Center (KDC) – This is a service that usually runs on the Domain Controller and it interacts with other Windows Server security services. The KDC provides two services: an authentication service and a ticket granting service.
The Kerberos authentication service is used by network clients to authenticate themselves to the domain and/or domain services. Once a client has authenticated, the authentication service sends the client a Ticket-Granting-Ticket (TGT) to be used with the Ticket-Granting-Service.
The Ticket-Granting-Service (TGS) accepts and validates TGTs from clients and issues TGS service tickets to the clients. TGS service tickets are provided to domain services by the clients to prove they are valid domain clients.
Kerberos authentication is based on the use of pre-shared keys. The KDC has access to any client’s private key via the domain controller’s security services (in most cases the KDC runs on the domain controller). The authentication process starts when a network client makes an authentication request (AS-REQ) to the KDC. If pre-authentication is enabled, the KDC will send an authentication reply (AS-REP) with a failure message asking the client to send an encrypted timestamp as part of the next AS-REQ. The timestamp is encrypted with the client’s password hash and sent to the KDC in a AS-REQ message. Since the KDC has the client’s password hash from the domain controller it can decrypt the timestamp and verify that the timestamp is within a given time frame. This is how the KDC verifies that the client is who it says it is. The KDC responds to the client with an AS-REP that has an encrypted TGT and an encrypted client blob. The client blob is encrypted with the client’s password hash so only the client can decrypt it. This is how the client knows the TGT came from the KDC. The encrypted client blob has details about the encrypted TGT as well as a session key that will be used in the next phase of authentication. The encrypted TGT is encrypted with the KDC’s secret (password hash) so the client cannot decrypt it. If pre-authentication is disabled, the KDC will respond without question to the initial AS-REQ and provide the encrypted TGT and session key.
Attackers note: Having pre-authorization disabled is a dangerous misconfiguration. Attackers can simply query the KDC for users with pre-authentication disabled and then request TGTs for each user. Then the TGT can be dumped from memory and used offline in a brute force password cracking attack. If the password is cracked, the attacker then has valid credentials for a domain user. This kind of Kerberos attack is known as AS-REQ Roasting and is trivial to perform. Pre-authentication should rarely be disabled and only in cases where it’s necessary for compatibility with legacy systems. Any accounts that have it disabled should have passwords that are sufficiently long and complex to thwart any password cracking attempts.
The client next decrypts the client blob to get a session key and makes a TGS request (TGS-REQ) to the TGS. The TGS-REQ includes the Service Principal Name (SPN) of the service it wants to access and preauth-data, which includes the encrypted TGT. The TGS extracts the encrypted TGT and decrypts it. Remember the TGS is just a service provided by the KDC and since the TGT is encrypted with the KDC’s secret, the TGS can decrypt it. This is also how the TGS determines that the TGT is legitimate since only the KDC knows the secret. Inside the TGT is a session key. This session key is the same key that the client received in the AS-REP from the KDC. The TGS prepares a TGS reply (TGS-REP) which includes a new service ticket (ST) which is encrypted with the SPN’s secret (password hash). The TGS-REP is encrypted with the session key and sent back to the client.
The client decrypts the TGS-REP to get the encrypted ST and sends it to the desired service. The service decrypts the ST with it’s secret and verifies the contents with the KDC. Once verified, the service grants access to the requesting client and authentication is complete.
Attackers note: If an attacker has valid domain credentials (user / password), they can attack the second phase of the Kerberos authentication process. The attacker can use the credentials to request a valid TGT and then request ST for any domain services with a SPN tied to a user account. Since the ST is encrypted with the service’s password hash, it can be dumped from memory and taken offline for a brute-force password cracking attack. Any cracked passwords will give the attacker new credentials that enable lateral movement and possible privilege escalation since many services run with elevated privileges. This attack is known as TGS-REP Roasting or more commonly as Kerberoasting.
Now that we have a baseline, let’s dive into CVE-2022-33679. CVE-2022-33679 is similar to AS-REP Roasting in the sense that it works against accounts that have pre-authentication disabled and the attack is unauthenticated meaning we don’t need (nor do we have) a client’s password. One detail of the initial client request (AS-REQ) for a TGT that I left out above, is how the encryption algorithm is chosen. In the AS-REQ, the client will list encryption schemes that it wants to use. The KDC will select a scheme to use and use it with the client’s secret to encrypt the session key in the AS-REP back to the client. There are several schemes that can be chosen to include a few that shouldn’t be used due to being considered weak encryption schemes. In this case, CVE-2022-33679 performs an encryption downgrade attack by forcing the KDC to use the RC4-MD4 algorithm and then brute forcing the session key from the AS-REP using a known plaintext attack. The Project Zero blog post goes into great detail on the weaknesses of RC4-MD4 and how the brute force attack works so I won’t repeat it here. Once the session key has been extracted you end up with the session key and a TGT… the two things you need to make a TGS-REQ to the TGS for a ST. Pretty awesome right?
The PoC posted to Github performs the attack and obtains a ST for a given service. It writes the ST out to a file in a credential cache (ccache) format. This format, if you’re not familiar with it (which I wasn’t), is a Kerberos standard format for representing TGTs and STs and is detailed in the Kerberos documentation. An interesting tidbit of information is that the Python Impacket library has several tools for interacting with Windows that accept credentials in ccache form. Let’s take a look at the PoC in action and then use the credentials cache with crackmapexec to access a given server’s shared folders.
The required options for the PoC script are a target and a server name. The target is a combination of the domain and a user account that has pre-authentication disabled. The server name is the server you want to authenticate to. In the example below, the domain is pod13.h3airange.internal
, the user is jsmith2
and the server is the domain controller dc01.pod13.h3airange.internal
. As the screenshot shows, the PoC requests an AS-REQ and receives the AS-REP. Then it brute forces the session key 1 byte at a time until it has all 5 bytes. It uses the session key and the TGT to request a ST from the TGS and then writes it out to disk. As a point of clarification, the script says that it receives a TGS and writes that to disk. What it means is it receives a TGS service ticket, which I’ve been calling a ST. That caused a bit of confusion for me initially which I’ll discuss in a bit below. In order to use the credentials cache with crackmapexec, we’ll need to export an environment variable called KRB5CCNAME
and set it to the ccache file created by the PoC. We’ll need to provide crackmapexec with the -k
flag which tells it to use the ccache file from the KRB5CCNAME
environment variable.
When I was tasked with adding CVE-2022-33679 to NodeZero, I knew very little about Kerberos. I knew it was used for authentication in Windows Domains and I knew of Kerberoasting and Golden Tickets but nothing about how any of it worked. At Horizon3, we pride ourselves in being “learn it alls”. Meaning we don’t know everything but we’re willing and able to adapt and learn on the fly as needed. I spent a few days reading up on Kerberos, everything from blog posts to the Kerberos standards documentation. I dove into existing vulnerabilities like AS-REP Roasting, Kerberoasting, Golden Ticket, Silver Ticket and Pass the Ticket. Once I felt I had a decent understanding of Kerberos and it’s existing weaknesses, I started to dig into the source code for the PoC and for Impacket’s tools like GetUserSPNs.py
and crackmapexec.py
. One thing I found consistently throughout the process was the terminology used for the different Kerberos stages and tickets wasn’t consistent from one place to the next. This is why I clarified above about the type of ticket the PoC wrote to disk.
As I was developing a module to work with the PoC, something kept bothering me. Not in a bad way, but it was a persistent feeling that there was more to this than just getting a service ticket. I had a bunch of puzzle pieces from several different Kerberos attacks and it felt like they hadn’t been combined to completeness yet. Initially, I chalked it up to having spent the previous days deep diving on Kerberos vulnerabilities and trying to keep them straight in my head but the more I worked on the module, the more it kept nagging. Here’s the gist of what kept bouncing through my head:
The question was, could these pieces be combined to do unauthenticated Kerberoasting? The short answer is yes…
Initially I thought maybe I could write the TGT to disk as a ccache file and use it later. This was early on when I still had a hard time keeping the different tickets and session keys straight. In this case I was confusing the session key recovered by the PoC with the client’s secret used to encrypt the AS-REP. I thought the recovered session key WAS the client’s password hash, not the session key used for communicating with the TGS. I lost more time than I care to admit trying to write the TGT to a ccache file. Impacket’s getTGT.py
had everything that would be needed. It has a GETTGT
class with a saveTicket()
function that takes a TGT and a session key (this is an example of where the terminology is confusing). saveTicket()
creates a credential cache from the TGT and then saves it to a file. Trying the TGT and a session key I had, just didn’t work. I went back and re-read the Project Zero blog and started to look at what was the PoC was doing. At some point I realized that the session key that saveTicket()
wants is actually the client’s password hash, which to be fair is used as the session key between the client and the KDC initially but is rarely called a session key. Regardless, the TGT could not be written to a ccache file without the client’s password.
Even though saving the TGT to a ccache file wasn’t possible, I still felt like there was more that could be done. I mean, I did have a valid TGT and session key after all. The next thing needed was a list of the Service Principal Names running under a user account in the domain. That shouldn’t be too hard as it is exactly what Impacket’s GetUserSPNs.py
does. Normally when Kerberoasting, we can use GetUserSPNs.py
with the -request
flag and valid credentials to get a list of SPNs and their hashes. We don’t have the valid username & password or the NTLM hash required but we do have a ccache file from our service ticket. It turns out GetUserSPNs.py
can use a ccache file as credentials when passed as an environment variable. Just use the -k
and -no-pass
flags and set the KRB5CCNAME
environment variable to the ccache file name. Let’s give it a try.
As you can see it works… sort of. Bummer. I thought I had connected all the dots but as the screenshot shows, only the SPNs get printed out. The hashes don’t. It was at this point that I almost gave up. Actually I did give up, for a while. I quit for the day, defeated but with the intent on trying again the next day with fresh eyes. However, getting so close and failing bugged me. It bugged me a lot. My brain cycled in an infinite loop, “Ok, you got the service names, now what? What can you do with that? You have a TGT and a session key and a ST and SPNs. What can you do with those?” Eventually the pieces came together and I thought of something I hadn’t tried. Since I had a TGT and a session key which are used to request access to a service and I now had a list of services (SPNs) with user accounts, I should be able to create new TGS-REQs, one for each service, and send the requests to the TGS. The TGS-REP returned would include a ST encrypted with the service’s secret (password hash) and I could dump those to crack offline.
With that idea, I made the following modifications to the original PoC.
Now when the script runs, it enumerates SPNs and dumps the krb5tgs hashes for an offline password cracking attack. Here’s the output from a complete run of the final script.
The Kerberoasted SPNs are in the section labeled KRBTGS in the screenshot. There are two in this example and each begins with $krb5tgs
. If we copy them out as is and put them into a separate file, we can attempt to crack them with JohnTheRipper.
You might be wondering why don’t we just access the services with the ST that is returned. The answer lies in the fact that the KDC isn’t responsible for determining if a client is authorized to access a service. It only authenticates a client to prove that it is a valid client and provides that validation to the service. It’s up to the service to determine if the user is authorized to access it and to what extent. The user in our example is jsmith2
and while it is a valid domain account, the account does not have permissions to access either of the services that we Kerberoast.
As bad as this could be, it’s fairly straightforward to mitigate. The two things that make this attack possible are pre-authentication being disabled and the RC4-MD4 encryption scheme. First, pre-authentication is enabled by default in Windows domains but is sometimes disabled for legacy systems or compatibility reasons. It should only be disabled for a individual accounts. Regular audits of domain accounts will identify any accounts that have pre-authentication disabled. Make sure it is actually required. Second, Microsoft has added a flag that disables the RC4-MD4 algorithm and while it’s recommended that RC4 be disabled, doing so can present it’s own set of problems. A final option is enforcing Kerberos Armoring (FAST) on all clients and KDCs in the environment. Whatever mitigations you put in place, make sure you trust but verify them.