Kerberoasting Without Mimikatz

Just about two years ago, Tim Medin presented a new attack technique he christened “Kerberoasting“. While we didn’t realize the full implications of this at the time of release, this attack technique has been a bit of a game changer for us on engagements. More and more attention has been brought to Kerberoasting recently, with @mubix releasing a three part series on the topic, Sean Metcalf covering it several times, and @leonjza doing a detailed writeup as well.

Thanks to an awesome PowerView pull request by @machosec, Kerberoasting is easier than ever using pure PowerShell. I wanted to briefly cover this technique and its background, how we’ve been using it recently, and a few awesome new developments.

Kerberoasting Background

I first heard about Kerberoasting from Tim at SANS HackFest 2014 during his “Attacking Kerberos: Kicking the Guard Dog of Hades” talk (he also released a Kerberoasting toolkit here). I’ll briefly paraphrase some technical detail of the attack, but I highly recommend you read Tim’s slides and/or Sean’s explanation for more detail. There’s also an excellent page of Microsoft documentation titled “Kerberos Technical Supplement for Windows” which finally clarified a few points involved in this process that were fuzzy to me.

Here’s my version of the obligatory “this is how kerberos works” graphic:


As far as how Kerberoasting fits into this process, this is how I understand it (if I am mistaken on some point please let me know!): after a user authenticates to the key distribution center (KDC, which in the case of a Windows domain is the domain controller) they receive a ticket-granting-ticket (TGT) signed with the domain krbtgt account that proves they are who they say they are. The TGT is then used to request service tickets (TGS) for specific resources/services on the domain. Part of the service ticket is encrypted with the NTLM hash of the target service instance. So how does the KDC determine exactly what key to use when encrypting these service tickets?

The Windows implementation of the Kerberos protocol uses service principal names (SPNs) to determine which service account hash to use to encrypt the service ticket. There are two “types” of service principal names in Active Directory: “host-based” SPNs that are linked to a domain computer account and “arbitrary” SPNs that are usually (but not always) linked to a domain user account.

As Microsoft explains, “When a new computer account is created in Active Directory, host-based SPNs are automatically generated for built-in services…In reality, SPNs are only created for the HOST service and all built-in services use the HOST SPN”. Put another way, “The HOST service represents the host computer. The HOST SPN is used to access the host computer account whose long term key is used by the Kerberos protocol when it creates a service ticket”. Here’s an example of a default computer account in my test domain:


You can see the HOST/WINDOWS1 and HOST/WINDOWS1.testlab.local SPNs for the WINDOWS1$ computer account. When a domain user requests access to \\WINDOWS1.testlab.local\C$, the KDC maps this request to the HOST/WINDOWS1.testlab.local SPN, indicating that the WINDOWS1$ machine account NTLM hash (which is stored both on WINDOWS1 locally and the NTDS.dit Active Directory database on the DC/KDC) should be used to encrypt the server part of the service ticket. The signed/encrypted ticket is then presented to WINDOWS1.testlab.local, which is responsible for determining whether the requesting user should be granted access.

From the Kerberoasting perspective, we generally don’t care about host-based SPNs, as a computer’s machine account password is randomized by default and rotates every 30 days. However, remember that arbitrary SPNs can also be registered for domain user accounts as well. One common example is a service account that manages several MSSQL instances; this user account would have a <MSSQLSvc/HOST:PORT> SPN for each MSSQL instance it’s registered for stored in the user’s serviceprincipalname attribute (Sean keeps an updated list of SPNs here). If we have an arbitrary SPN that is registered for a domain user account, then the NTLM hash of that user’s account’s plaintext password is used for the service ticket creation. This is the key to Kerberoasting.

Obligatory “So Why Does This Matter?”

Because of how Kerberos works, any user can request a TGS for any service that has a registered SPN (HOST or arbitrary) in a user or computer account in Active Directory. Remember that just requesting this ticket doesn’t grant access to the requesting user, as it’s up to the server/service to ultimately determine whether the user should be given access. Tim realized that because of this, and because part of a TGS requested for an SPN instance is encrypted with the NTLM hash of a service account’s plaintext password, any user can request these TGS tickets and then crack the service account’s plaintext password offline, without the risk of account lockout!

To reiterate, any domain user account that has a service principal name set can have a TGS for that SPN requested by any user in the domain, allowing for the offline cracking of the service account plaintext password! This is obviously dependent on a crackable service account plaintext, but luckily for us service accounts tend to often have simple passwords that change very infrequently. ¯\_(ツ)_/¯

As an added bonus, Tim mentions on slide 18 of his presentation deck:



“Old School” Kerberoasting

Tim’s outlined approach/toolkit used a combination of toolsets to request tickets, extract them from memory (using Mimikatz), and transform them into a crackable format. In general, the process (up until recently) went as follows:

  • Enumerate the domain accounts with SPNs set- either with Tim’s GetUserSPNS.ps1 script, Sean’s Find-PSServiceAccounts.ps1 script, or PowerView’s “Get-NetUser -SPN“.
  • Request TGSs for these specific SPNs with the builtin Windows tool setspn.exe or the .NET System.IdentityModel.Tokens.KerberosRequestorSecurityToken class in PowerShell.
  • Extract these tickets from memory by invoking the kerberos::list /export Mimikatz command , with the optional base64 export format set first. The tickets were then downloaded, or the base64-encoded versions pulled down to the attacker’s machine and decoded.
  • Begin offline password cracking with Tim’s, or extract a crackable hash format from the raw ticket with John the Ripper’s

xan7r branched Tim’s toolset and added an autokerberoast.ps1 script that automated large components of this process. Also, @tifkin_ wrote a Go version of a TGS cracker that functioned a bit faster than the original Python version.

“New School” Kerberoasting

A few recent(ish) things really simplified our usage of Kerberoasting on engagements. First, Michael Kramer added the KRB5TGS format to John the Ripper in September of 2015. Second, @Fist0urs committed the same algorithm to Hashcat in Febuary 2016, opening the door for GPU-based cracking of these tickets. This was really a watershed for us, as it greatly expanded the range of service account passwords we could crack. And finally, Matan Hart (@machosec)’s pull request to PowerView removed the Mimikatz requirement.

@machosec realized that .NET class KerberosRequestorSecurityToken used in previous approaches also had a GetRequest() method, which returns the raw byte stream of the Kerberos service ticket. With a bit string manipulation, Matan was able to easily extract out the encrypted (i.e. the crackable hash component) of the TGS. We are now no longer dependent on Mimikatz for ticket extraction!

I recently rolled the necessary functions into a single, self-contained script that contains the necessary components from PowerView (this has also been updated in Empire). We are currently in the process of refactoring large components of PowerSploit, and the updated functions will be posted here after the changes are published. This custom-rolled script includes the Invoke-Kerberoast function, which wraps the logic from Get-NetUser -SPN (to enumerate user accounts with a non-null servicePrincipalName) and Get-SPNTicket to request associated TGS tickets and output John and Hashcat crackable strings. For now, here’s what the output of the script looks like:


It also works across domains!


By default, the John format is output, but -OutputFormat Hashcat will output everything Hashcat-ready. Note that the -AdminCount flag only Kerberoasts accounts with AdminCount=1, meaning user accounts that are (or were) ‘protected’ and, therefore, almost always highly privileged:


And here’s how the updated Empire module looks:


Note that for non-Empire weaponizations, as PSObjects are output, you will need to pipe the results to Format-List or ConvertTo-Csv -NoTypeInformation in order to preserve the information you want displayed. You can then crack these tickets as @mubix described in his third post.

Again, the self-contained, PowerShell 2.0-compliant script is on my Gists here. Hopefully this is as much use to you as it has been for us over the past few months!

10 thoughts on “Kerberoasting Without Mimikatz

  1. Hey harmj0y,

    first of all: Thanks for the nice write-up!

    I have added the module to empire and tested it against my lab domain.
    As there was no SPN available I added a user “spntest” and a spn using “setspn -U -S http/spntest spntest”. The hashcat formated hash was retrieved by the Invoke-Kerberoast module without any problems.
    I handed the hash over to my windows based hashcat machine using “hashcat64.exe -m 13300 hashfile D:\wordlists\testlist”. The wordlist was only containing the correct password and four dummies.
    Surprisingly hashcat recognized the hash as Kerberos 5 TGS-REP etype 23, finished its run without any errors, but did not recover the password from it.
    I suspected the output format of the module to be the problem as it needs some massaging to fit into the one liner hashcat expects but after retesting it for the third time I am pretty sure that the input to hashcat is correct.
    Could you give me a hint how you were able to utilize hashcat for the cracking stage?
    Thanks in advance!

  2. Your Invoke-Kerberoast from the gist you linked seems to have a bug somewhere. When I run it with no -Identity argument it lists accounts with the wrong SPN/hash even though there was an error retrieving the information. So basically, it’s outputting objects with incorrect data when it should output nothing for that identity because there was an error.

    For example in one instance the same SPN/hash is listed for 13 different accounts. Only the first of the 13 is correct. The other accounts have SPNs/hashes of their own, but obviously they are different. Seems like maybe you’re reusing some variables from identity to identity where if an operation on an identity fails the script still outputs an object but it just contains the SPN/Hash of the last identity that succeeded.

    For the record the error it gives on the identities that don’t work is below:
    New-Object : Exception calling “.ctor” with “1” argument(s): “The NetworkCredentials provided were unable to create a
    Kerberos credential, see inner exception for details.”
    At C:\tools\Invoke-Kerberoast.ps1:555 char:23
    + … $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorS …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

  3. Great work. I’ve also done some work on Kerberos and found a couple of things that you might be interested in. One, that you can use also enumerate users by requesting an AS-REP and reading the error codes. Two, you can crack the AS-REPs that do not require pre-authentication in the same way as the TGS-REPs.

Leave a Reply

Your email address will not be published. Required fields are marked *