Kerberoast with OpSec

You must have been thinking… Is this another blog post about Kerberoasting? Well yes and no. During this time, we will be discussing how to Kerberoast accounts, while trying to stay under the radar from a defender’s perspective. The focus is primary on the technique itself, and not the fact that I’m using PowerShell 😉

Kerberoasting is an technique that allows an attacker to take advantage of how service accounts leverage Kerberos authentication with Service Principle Names (SPN). It allows every authenticated user to request a Service Ticket for any account that has an SPN registered. Once an user is requesting a Service Ticket from an account. An Domain Controller will return an encrypted ticket, which is encrypted by using the password hash of the associated account with the SPN.

Since there are tons of blog post around Kerberoasting. I don’t want to spent time with covering all of the same stuff over and over again. Let’s dive right into it!

Example

Here we have an account that has an SPN, so basically this means. Every authenticated user can now request a Service Ticket for this specific account, export the ticket offline. And attempt to crack it, without making any communication to a Domain Controller. There is not really a way to prevent this attack, besides from removing the SPN if it’s not required.

SPN Discovery without OpSec

First thing, we can do is. Discover any account that has an SPN. This can be done by running a simple LDAP query against the directory. This LDAP query will look for any enabled account with an SPN.

([adsisearcher]'(&(samAccountType=805306368)(servicePrincipalName=*)(!samAccountName=krbtgt)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))').FindAll()

An other option is to look for enabled accounts with a SPN, but filtering on accounts that have the adminCount attribute set to 1. This may indicate that the account is part of a sensitive built-in group within AD. This can be Domain Admins, Enterprise Admins, and so on.

([adsisearcher]'(&(samAccountType=805306368)(adminCount=1)(servicePrincipalName=*)(!samAccountName=krbtgt)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))').FindAll()

OpSec mistake

The problem with this approach is, that we expose an LDAP search filter. There are EDR products and Threat Hunters, who are actively looking for the servicePrincipalName=* wildcard filter.

Requesting Service Tickets without OpSec

We will be demonstrating how OpSec mistakes can occur, when Kerberoasting accounts. An common example is to request multiple Service Tickets at once.

Rubeus.exe kerberoast /outfile:C:\Temp\hashes.txt

OpSec mistake

An OpSec mistake is that once someone decides to request multiple Service Tickets at once. It will generate multiple event ID, 4769 on a Domain Controller. An great use-case for a network defender would be to baseline the amount of Service Tickets an user typically requests in a day.

Requesting Service Ticket of Honeypot account

The second OpSec mistake we will discuss is, requesting a Service Ticket from an account that is used as a honeypot account.

OpSec mistake

At the result, we can see a user requesting a Service Ticket from an account that is used as a honeypot account. This is a great use-case for a network defender, which is creating a fake honeypot account, register an SPN, and monitor when someone is requesting a Service Ticket of it.

Requesting Service Ticket with ‘downgrade’ encryption

The account that we want to Kerberoast has AES encryption enabled for example, but we can still request RC4 tickets. Here is an example of requesting a Service Ticket with RC4 encryption type, while AES is enabled.

Rubeus.exe kerberoast /user:svc_kerberoast /tgtdeleg

OpSec mistake

While the account has AES enabled, so we shouldn’t expect to see RC4 encryption (0x17) in the event logs. This could trigger an alert.

Enumeration with OpSec

First, we need to enumerate all the Organizational Units in the domain.

([adsisearcher]'(objectCategory=organizationalUnit)').FindAll()

The second thing, we have to do is. Listening accounts in an OU that we consider ‘interesting’. During this example, we decided to pick the following OU:

  • OU=SVC Accounts,DC=contoso,DC=com

Based on the naming convention, we may assume that this OU contains accounts with a SPN. Now we have to list the accounts that resides in the OU with the LDAP attributes, we’re interested in.

$ChildItems = ([ADSI]"LDAP://OU=SVC Accounts,DC=contoso,DC=com");
$ChildItems.psbase.Children | Format-Table samAccountName, servicePrincipalName

At the result, we can see an account. It contains a MSSQL SPN at the Server machine, which looks interesting from the beginning. However, we should never assume that it’s a legitimate SPN.

SPN is the unique (in entire Forest) identity for a Service, mapped with a specific service account in a server. This means that we should look if this server exists in the domain.

([adsisearcher]'(&(objectCategory=computer)(cn=Server))').FindAll()

At the result, we can see that there’s a machine account that is associated with the Server machine. This should give us a bit of extra information that it could be a legitimate SPN.

Validating SPN account

In this example, we are interested in the svc_kerberoast account, but we are not sure if it’s a fake account or not. The best approach for this would be to query the relevant LDAP attributes.

$user = [adsi]"LDAP://CN=svc_kerberoast,OU=SVC Accounts,DC=contoso,DC=com"
[PSCustomObject] @{
samAccountName = $user.samAccountName.Value
Description = $user.description.value
whenCreated = $user.whenCreated.Value
whenChanged = $user.whenChanged.Value
servicePrincipalName = $user.servicePrincipalName.Value
userAccountControl = $user.userAccountControl.Value
pwdLastSet = [datetime]::FromFileTime($user.ConvertLargeIntegerToInt64($user.pwdLastSet.value))
lastLogon = [datetime]::FromFileTime($user.ConvertLargeIntegerToInt64($user.lastLogon.value))
}

At the result, we should pay attention to these kind of attributes. This will help us to get a better insight, whether this is a fake account or not. Take a close look to attributes like, whenCreated, userAccountControl, pwdLastSet, lastLogon, etc.

Check Encryption Type

The msDS-SupportedEncryptionTypes attribute on an account is good to check as well, because this will show whether the Service Ticket is using RC4 or AES as an encryption type. Keep in mind that, when AES is set on an account. We can still request a RC4 encrypted ticket… However, it’s not a smart thing to do, because this will get us caught.

([adsisearcher]'(&(objectCategory=user)(cn=svc_kerberoast))').FindAll().Properties

Kerberoasting

The final step is to Kerberoast the specific user that we have in mind. In this case, we have done all the OpSec checks. It is a legitimate SPN account. We will be using the Get-TGSCipher.ps1 script that allows us to extract the Service Ticket into a format that supports John the Ripper, Hashcat, etc.

Get-TGSCipher -SPN "HTTP//Kerberoast.me" -Format John

Cracking the password

Now the last thing is to crack the password

john.exe C:\Temp\hashes.txt --wordlist=C:\Temp\wordlist.txt

At the result, we can see that we have cracked the password.

OpSec

We have now avoided the suspicious LDAP search filter that contains the servicePrincipalName=* wildcard. Threat Hunters love to filter on this, so we have now avoided it.

Reference

One comment

  • Octavian Augustus

    I`have been reading this article for 2 weeks and still I`m amazed with all this details! That is very thorough examination of that type of attack!

    Like

Leave a comment