Downgrading Kerberos Encryption & Why It Doesn’t Work In Server 2019

This took me a while to get my head around recently so I figured I’d document it here in case it helps someone else.

Thanks to @exploitph for answering several questions about this and @harmj0y for writing this blog post which explains the entire process. Whilst that post does go into a lot of detail, it also covers several other things as well and at the time it was written Windows Server 2019 was not a thing. So I decided to make a post that keeps things relatively brief and explains why this trick no longer works in Server 2019.

A quick Kerberos refresher

Here’s a simplified description of some things you need to know for this post to make any sense:

  • A TGT is a special ticket that a user receives from a domain controller when they first log on
  • You use the TGT to request service tickets, which let you access services like SQL server, SMB, etc
  • Service tickets are encrypted using the password of the user/computer account that runs that service
  • Kerberoasting is the name of an attack where we request service tickets and then try to crack them to get the service account’s password

If you don’t fully understand any of these things then my Kerberos video and Kerberoasting video should help.

Why do we want to get our own TGT?

There are some built in functions in Windows that let us request Kerberos authentication to a particular service, but the problem for us as an attacker is that these APIs are designed to be opaque. Windows handles everything for us, so we can’t directly control anything in these requests (other than which service we want a ticket for).

This means that the service tickets we get back will be encrypted with whatever level of encryption Windows decides to use. If the user account running the service is configured to support AES encryption then that is what will be used to encrypt the service tickets, which makes the process of cracking the passwords much slower and practically impossible in some cases.

If we could get hold of a usable TGT, then we could use it to craft our own service ticket requests and specify that they only support the weaker RC4 encryption.

Getting a user’s TGT and using it for Kerberoasting

If we know the user’s password (or even just a hash of their password) then we can request a TGT with Rubeus like so:

rubeus.exe asktgt /domain:mydomain.local /user:mydomain.local\username /password:Passw0rd

or in the GUI version:


Then all we need to do is copy the base64 that it gave us (or export to a .kirbi file) and then supply that to the Kerberoast function.

First of all though let’s look at what happens if we try Kerberoasting without supplying our own TGT, just using the normal Windows APIs to request service tickets:

Rubeus3

When we let Windows handle things for us we get a service ticket encrypted using the highest level of encryption that the service account supports (in this case AES-128, as the account supports RC4 and AES-128).

Now let’s try again but supply our own TGT that we exported earlier:

Rubeus6

As you can see with this method we have successfully downgraded the encryption to RC4, which will be much easier to crack.

So problem solved… right?

Well in this case yes, but it turns out there are two scenarios where this approach won’t work.

Problem 1: The DC we request a TGT from is running Windows Server 2019

If we try the same technique to downgrade the encryption but the domain controller we get a TGT from is running Server 2019 or newer, something different happens.

We supply our own TGT and send the same request as before that says we only support RC4 encryption, as you can see in Wireshark (ARCFOUR = RC4):

But this time the server responds with a service ticket encrypted with AES-128 instead of RC4:

Rubeus5

Unfortunately it turns out that any DC running Server 2019 will ignore the encryption type that the client requested and will always use the highest level of encryption that the service account supports.

I’m not aware of any workaround for this at the moment. You’re stuck with AES encryption if the service account is marked as supporting it. Luckily AES is not enabled on new user accounts by default, but it is very easy to enable it.

Problem 2: We have code executing as the user but don’t know their password

Without knowing the user’s password (or a hash of their password) we can’t request a TGT for them like we did before.

Luckily there is another way we can get their TGT, which is known as the TGT delegation trick.

This trick takes advantage of the fact that domain controllers are configured to allow unconstrained delegation by default:


This just means that any service running on this machine is allowed to impersonate users that authenticate with it – a perfectly legitimate thing that services often need to do.

However, for the server to be able to impersonate a user it needs a TGT for that user. So when a client gets a ticket for a service that supports delegation it also sends an additional request to get a new TGT that can be passed to that service for it to use for impersonation.

As an attacker executing code as a domain user we can mimic this behaviour just using the standard Windows APIs, but instead of sending the new TGT to a service we just extract it and use it for Kerberoasting like we did in the original demo.

This is what happens if you use the /tgtdeleg option when Kerberoasting with Rubeus, or select this option in the GUI


This is a clever solution to the problem (assuming you can get Rubeus to run as the user without getting blocked by AV) but as mentioned above it won’t work with 2019 DCs.

Further reading

I simplified some things in this post as I didn’t want it to be overly long and complex (not sure I succeeded there…) but if you want to understand specific details then these resources should help:

 

How Windows Stops Kerberos Usernames Being Case Sensitive

We’re all used to Windows treating usernames as not being case sensitive, i.e. a username of JOE.BLOGGS is the same as joe.bloggs. However this idea falls apart when you use that username as part of the salt for AES encryption, which is case sensitive. This is exactly what happens when we perform Kerberos preauthentication using AES encryption (the default encryption level in current versions of Windows).

So why doesn’t this cause problems when we supply the username in the incorrect case?

For example:

In AD we have a user setup with a username that has uppercase letters in it, in this case “joe.BLOGGS”

Then we run a command like Net Use and specify the username in all lowercase:

Net Use \\server.kerb.local\Share /user:kerb.local\joe.bloggs

This does use Kerberos and does use the correct case username for the AES salt even though we didn’t supply it like that, but how?

The short answer:

Windows sends an authentication request without any encrypted preauth data to begin with (so the AES salt is not needed yet). The DC responds with an error saying preauth is required but it also includes the username in the exact same case it is stored as in AD. So then Windows just uses that as the salt for the encryption and tries again, which should be successful.

The long answer:

When we execute something like the Net Use command above, regardless of whether we specify the username in the correct case this is what happens:

1: Windows sends an authentication request (AS-REQ) without encrypting any preauthentication data, but telling the server that it supports AES encryption.

2: The server responds with a message that contains the error “PREAUTH_REQUIRED” but importantly it also includes the following section because the original request said it supported AES:

As you can see this tells us the exact salt to use for this user, which is the domain name in uppercase (KERB.LOCAL) followed by the username in the exact same case that was in the user’s AD account (joe.BLOGGS).

3: So now Windows can just send another AS-REQ but this time using the correct salt to encrypt the preauth data (which is a timestamp to avoid replay attacks, as I explained in this video):

4: This time the server responds with an AS-REP which contains the user’s TGT and then they can go ahead and use that to request service tickets.

Notes

If the user account has preauth disabled then none of this matters as the client doesn’t need to encrypt anything using their username as a salt. So after the first AS-REQ the server just responds with the AS-REP containing the user’s TGT (message 4 in the info above).

Also some attacker tools skip straight to the second AS-REQ that includes encrypted preauth data, so they do need the username to be in the correct case. For example if you the “brute” command in Rubeus and supply usernames and passwords but with the usernames not matching the case they are in AD, it will tell you that no valid credentials were found even though you did have a correct username and password combo. I’m hoping to work around this in my Rubeus GUI tool but I’ll make another post about that soon. EDIT: After way more work than I anticipated, I’ve now made my Rubeus GUI tool handle this correctly so usernames are no longer case sensitive 🙂

Lastly, if any of the Kerberos lingo above didn’t make much sense to you, check out my in depth explanation of Kerberos here:

Kerberos Protocol Explained

This blog post is intended to supplement the Kerberos explanation video that just went live on my Youtube channel:

As such, I’m not going to explain everything in detail here. This post is intended as a quick reference for all of the diagrams I made for that video. Hopefully they can be useful on their own even if you don’t watch the video, but of course I’d encourage you to watch it to get a full explanation of everything here.

So let’s start off with the simple high level summary of the Kerberos authentication process:

Step 1 and 2 in the diagram above happen once, when the user logs on to their PC. Steps 3 and 4 happen the first time they try to authenticate with the network service (SQL Server in this example). The service ticket they receive in step 4 will get cached, so then step 5 happens every time they access the service and uses that cached ticket (until they log off or until the service ticket expires and then they need to repeat step 3 and 4 again).

Now for a slightly more in depth look at that same process:

To understand how this really works, we need to look at the network messages that get sent for each of those steps.

First of all we have the AS-REQ and AS-REP which cover step 1 and 2 in the previous diagrams:

Then for step 3 and 4 we have the TGS-REQ and TGS-REP messages:

Then finally the AP-REQ message for step 5. Note that this message won’t usually be easy to see in network captures because it will be sent over whatever protocol the client communicates with the network service with (e.g HTTPS, SQL’s network protocol, or some proprietary protocol created just for this service):

Of course there’s more to some of these structures, but I’ve picked out the interesting parts and tried to keep things as simple as possible whilst still being accurate.

Towards the end of the video mentioned at the start of this post, you’ll see how each of these diagrams relate to a real world Wireshark network capture of this whole process happening. So yeah, go watch that 🙂