- Kerberos OPSEC: Offense & Detection Strategies for Red and Blue Team – Introduction
- Kerberos OPSEC: Offense & Detection Strategies for Red and Blue Team – Part 1 : Kerberoasting
- Kerberos OPSEC: Offense & Detection Strategies for Red and Blue Team – Part 2 : AS_REP Roasting
One of the most common attacks is Kerberoasting. This is a service ticket request (TGS) for Active Directory domain accounts with SPNs (Service Principal Names). Any domain user can request a Service Ticket (ST) for any service from the KDC. Part of the information retrieved is encrypted with a derivative of the service account’s secret associated with the SPN. Very often, service accounts are machine accounts, with long and very complex passwords, making a bruteforce attack very difficult, if not impossible, to carry out in a limited timeframe.
Summary
Attack explanation
Sometimes services are run via domain accounts with passwords that have been set by humans, which can result in the use of weak passwords. It is then possible to request a ticket for these accounts and attempt to break the potentially weak secret derivation via an offline bruteforce attack in order to recover the service account’s cleartext password.
When a user seeks access to one of the domain’s services, it presents its TGT to the KDC in order to authenticate himself and obtain a TGS. The obtained KRB_TGS_REP response is composed of two parts:
- The first part is the TGS whose content is encrypted with the secret of the requested service account
- The second part is a session key which will be used between the user and the service. It is encrypted using the requesting user’s secret
The first part is one we are interested in since it can be cracked in order to potentially retrieve the service account’s password.
For more details, see the following blogpost: https://en.hackndo.com/kerberoasting.
Recon
As a reminder, we have 2 accounts with SPNs:
- R5-D4, which supports RC4, AES128, AES256 encryption algorithms
- Qi-Ra, which only supports the RC4 encryption algorithm
Via the Rubeus tool, you can query all accounts with SPNs and obtain a KRB_TGS_REP in a format easily usable by password cracking tools (john or hashcat to name but a few) with the command Rubeus.exe kerberoast /nowrap:
Some information are interesting:
- The LDAP query filter for finding SPNs is (&(samAccountType=805306368)(servicePrincipalName=*)(!samAccountName=krbtgt)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))):
- (samAccountType=805306368) to get domain users
- (servicePrincipalName=*) to query all possible SPNs that are present in the domain
- (!samAccountName=krbtgt) the requested accounts are NOT the KRBTGT account
- (!(UserAccountControl:1.2.840.113556.1.4.803:=2)) is a Bitwise AND comparison, checking if the 2nd bit of the user’s UAC is 1, meaning that the user is disabled or locked. The
1.2.840.113556.1.4.803
correspond to the Bitwise AND comparison (same as the&
operator)
Ticket Cipher
- We can see that the R5-D4 account supports AES128 and AES256, so the ticket obtained is in AES256 (the « 18 » at the beginning of the obtained string) the highest encryption level available.
- For the other account, Qi-Ra, we can see that the ticket obtained is in RC4 because only RC4 is supported (the « 23 » at the beginning of the obtained string).
IIf we look at the local tickets, we can see that they have been correctly recovered and cached:
- For « FakeService » in RC4:
- For « MSSQL » in AES256:
However, breaking AES256 can be very time-consuming, so it may be worth doing what’s known as encryption downgrade, so for accounts using AES, it’s possible to request an RC4 ticket, which is much easier to break, with the /tgtdeleg option in Rubeus.
Warning: it may happen in more mature environments that RC4 is completely disabled for all domain accounts. As a result, an RC4 ticket request could be quickly detected (as well as not working).
Furthermore, since Windows 2019, it is no longer possible to perform this encryption downgrade. In fact, when using the /tgtdeleg option in Rubeus, the ticket received is still in AES256:
To date (and to our knowledge), there is no way of circumventing this mechanism.
Process used
By default, the process used to perform the Kerberoasting action with Rubeus is LSASS.exe:
Ticket options
A final element to be observed in this attack is the ticket request options. Ticket requests made during a Kerberoast attack with the Rubeus tool have the following option string in the `TicketOptions` parameter:
If we transform 0x40800000 into binary, we get:
01000000 10000000 00000000 00000000
Then read this from left (big endian) to right to get the following options (starting from 0):
- 1 : Forwardable
- 8 : Renewable
In the Rubeus code, Kerberoasting without any particular option is performed with the .NET function GetRequest() of the class KerberosRequestorSecurityToken:
// the System.IdentityModel.Tokens.KerberosRequestorSecurityToken approach and extraction of the AP-REQ from the
// GetRequest() stream was constributed to PowerView by @machosec
System.IdentityModel.Tokens.KerberosRequestorSecurityToken ticket;
if (cred != null)
{
ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn, TokenImpersonationLevel.Impersonation, cred, Guid.NewGuid().ToString());
}
else
{
ticket = new System.IdentityModel.Tokens.KerberosRequestorSecurityToken(spn);
byte[] requestBytes = ticket.GetRequest();
This method doesn’t allow you to specify the various options of the desired ticket, so it seems that the TicketOptions field, when this method is used, will always be set to 0x40800000.
By using certain parameters, it is possible to have other options, for example, for the /rc4opsec parameter, allowing tickets to be requested only for users with the RC4 algorithm available:
The ticket options will then be 0x40800010:
If we transform 0x40800010 into binary, we get:
01000000 10000000 00000000 00010000
Then read this from left (big endian) to right to get the following options (starting from 0):
- 1 : Forwardable
- 8 : Renewable
- 27 : Renewable Ok
If we look at the corresponding Rubeus code, specifying the /rc4opsec parameter is equivalent to using the /tgtdeleg parameter to retrieve the TGT for the current user:
else if (useTGTdeleg || String.Equals(supportedEType, "rc4opsec"))
{
Console.WriteLine("[*] Using 'tgtdeleg' to request a TGT for the current user");
byte[] delegTGTbytes = LSA.RequestFakeDelegTicket("", false);
TGT = new KRB_CRED(delegTGTbytes);
Console.WriteLine("[*] RC4_HMAC will be the requested for AES-enabled accounts, all etypes will be requested for everything else");
}
Source: https://github.com/GhostPack/Rubeus/blob/5db3150243649ed737170736767cda3e6ba9dc28/Rubeus/lib/Roast.cs#L313
This will make the ST recovery method different:
// request the new service ticket
byte[] tgsBytes = Ask.TGS(tgtUserName, domain, ticket, clientKey, etype, spn, requestEType, null, false, domainController, false, enterprise, false, false, null, tgtDomain);
If we go to the definition of the Ask.TGS function, we can see that it calls the TGS_REQ.NewTGSReq function:
byte[] tgsBytes = TGS_REQ.NewTGSReq(userName, domain, service, providedTicket, clientKey, paEType, requestEType, false, targetUser, enterprise, roast, opsec, false, tgs, targetDomain, u2u);
Source: https://github.com/GhostPack/Rubeus/blob/master/Rubeus/lib/Ask.cs#L377
In this function, the ticket request is instantiated with the following code:
TGS_REQ req;
if (u2u)
req = new TGS_REQ(!u2u);
else
req = new TGS_REQ(!opsec);
This calls TGS_REQ, which is defined as follows:
public TGS_REQ(bool cname = true)
{
// default, for creation
pvno = 5;
// msg-type [2] INTEGER (12 -- TGS)
msg_type = (long)Interop.KERB_MESSAGE_TYPE.TGS_REQ;
padata = new List<PA_DATA>();
// added ability to remove cname from TGS request
// seemed to be useful for cross domain stuff
// didn't see a cname in "real" S4U request traffic
req_body = new KDCReqBody(c: cname);
}
This finally calls KDCReqBody, which defines the following ticket options:
public KDCReqBody(bool c = true, bool r = false)
{
// defaults for creation
kdcOptions = Interop.KdcOptions.FORWARDABLE | Interop.KdcOptions.RENEWABLE | Interop.KdcOptions.RENEWABLEOK;
This corresponds to the 0x40800010 options observed earlier.
However, let’s take the /rc4opsec parameter for example, we can observe that by changing the options, given that the ticket request is made directly from the Rubeus code and not from a function provided by the Windows API, the traffic will be associated with the sacrificial process launched by our beacon, WerFault.exe here, as our current user C3-PO :
Detecting « classic » Kerberoasting
Kerberoasting generates a number of traces that may be worth keeping an eye on.
LDAP Reconnaissance
In the domain controller logs (assuming the audit policy allows this):
- We can see the LDAP query used to enumerate SPNs (event with ID: 1644):
Note: Security solutions or Threat Hunters have been known to actively search the logs for a request of this type using the servicePrincipalNames=* filter.
- In addition, querying a large number of tickets in a very short time will generate many events with ID 4769:
Ticket cipher
In events with ID 4769, we can also see the encryption algorithm used (the Ticket Encryption Type field):
Ticket options
As well as the ticket options (the Ticket Options field):
Process used
When changing the options, the Kerberos traffic is not passing through the LSASS.exe process:
We have already seen that some EDR solutions can detect Kerberos communications from a non-legitimate process.
Detection ideas
Here are a few ideas to detect kerberoasting attacks:
- Monitor events with ID 1644 by filtering on the servicePrincipalNames=* string of the LDAP request. Although theoretically possible, rules based on LDAP Windows events are rarely implemented because the volume of LDAP logs to be collected is very large (Alternatives are emerging, with commercial solutions such as Microsoft Defender for Identity, or custom rules implemented on endpoint security solutions)
- Check that Kerberos traffic on port 88 goes through the LSASS.exe process, except in certain legitimate cases that we will see later, it should always be the case.
- Check the number of ticket requests in a short space of time by monitoring the number of events with ID 4769
- In events with ID 4769:
- Check the algorithm of requested tickets against the algorithms available for the accounts to monitor downgrade encryption attempts (eg: an attacker that request RC4 ticket while all tickets should be encrypted in AES by default);
- Check that ticket options are not « out of the ordinary », the most common being (according to our observations and Microsoft documentation):
- 0x40810000 (the most common ticket request we have seen);
- 0x40810010 (this is the default option used by the GetUserSPNs.py tool in the impacket suite, and can be used in legitimate cases, but less frequently than 0x40810000);
- 0x40800000 (this is the default option used by the Rubeus tool as described earlier in this article, and is used in many legitimate cases, but less than 0x40810000).
- Check the targeted services in correlation with the number of events with ID 4769, as in most cases, the services for which tickets will be requested will be domain users and not machine accounts during a Kerberoast attack. It may therefore be useful to check whether the accounts for these services end with the ‘$’ character (note that this can lead to false positives if the processing rule is incorrectly set up. For example, tickets for the KRBTGT account are very often requested, but this account does not end with the ‘$’ character):
It is important to study the usual behavior of your Active Directory infrastructure before establishing these rules as they can lead to false positives, especially the ticket options.
OPSEC turnaround
Taking into account the detection elements presented above, a number of elements need to be taken into account in order to make Kerberoasting actions stealthier.
LDAP Reconnaissance
- First of all, we need to avoid generating the event with the ID 1644 with the servicePrincipalNames=* filter. A good way to do this is to identify accounts with an SPN in another way (e.g. with a BloodHound collection with the CollectionMethod -DCOnly made beforehand), then perform Kerberoasting on a single, well-targeted account:
Note that BloodHound still uses this LDAP filter, however, it enables the attacker to avoid correlation between this event and the kerberos ticket requets in a very short amount of time.
- Performing this on a single account at a time will therefore only generate a single event with the ID 4679:
We have already seen that generating a large number of events with the ID 4679 in a short space of time can be suspicious and led to a detection, so it is stealthier to separate Kerberoasting actions by several hours, or even to perform just one per day, in order to blend in as much as possible.
Ticket Cipher
- Secondly, as the encryption algorithm of the requested ticket can be monitored, care should be taken when requesting tickets to ensure that only the highest algorithm available is used for each account. By default, with the Rubeus tool, the highest algorithm available is requested:
Note that using a robust encryption algorithm such as AES256 will make the cracking process more difficult than with the RC4 algorithm.
Ticket options
- Check the ticket request options. In a classic AD environment, ticket requests most generally have the options 0x40810000 (from what we have been able to observe, this may be different in your environment), the one used by Rubeus in a default Kerberoasting is 0x40800000. It is possible to change these options by playing with Rubeus parameters, for example with the /rc4opsec option as specified above to obtain the 0x40800010 options. To obtain the most common option, namely 0x40810000, we added an option to Rubeus:
// In Kerberoast.cs
namespace Rubeus.Commands
{
public class Kerberoast : Icommand
{
public static string CommandName => "Kerberoast;
public static bool blendin = false;
[...]
if (arguments.ContainsKey("/blendin"))
{
blendin = true;
}
// In Roast.cs
else if (useTGTdeleg || String.Equals(supportedEType, "rc4opsec") || Rubeus.Commands.Kerberoast.blendin==true)
{
Console.WriteLine("[*] Using 'tgtdeleg' to request a TGT for the current user");
byte[] delegTGTbytes = LSA.RequestFakeDelegTicket("", false);
TGT = new KRB_CRED(delegTGTbytes);
Console.WriteLine("[*] RC4_HMAC will be the requested for AES-enabled accounts, all etypes will be requested for everything else");
}
// In KDCReqBody.cs
public KDCReqBody(bool c = true, bool r = false)
{
// defaults for creation
if (Rubeus.Commands.Kerberoast.blendin == false)
{
kdcOptions = Interop.KdcOptions.FORWARDABLE | Interop.KdcOptions.RENEWABLE | Interop.KdcOptions.RENEWABLEOK;
}
else
{
kdcOptions = Interop.KdcOptions.FORWARDABLE | Interop.KdcOptions.RENEWABLE | Interop.KdcOptions.CANONICALIZE;
}
Note: This is an example of a simple modification to illustrate the point made here. It does not take into account many different scenarios and is not useable in a production scenario.
Therefore, when using the /blendin parameter, the ticket options will be 0x40810000:
However, modifying ticket options also changes the ticket request method, so the Kerberos protocol network traffic on port 88 will no longer pass through the LSASS.exe process.
Process used
During our research, we noticed that in some cases, traffic on port 88 « legitimately » does not pass through the LSASS.exe process:
- Depending on the language used to create an application, modules for Kerberos can be reimplemented by following the corresponding RFC. For example, taking Rubeus’s code, most of the Kerberos actions can be « manually » coded and traffic may not pass through the LSASS.exe process, even though we have not seen this in a real environment yet, it may be worth studying the applications present on the machine to find out which one to use for ticket requests;
- If an application, for example in HTTP, were to use port 88, this could generate false positives. This is why detection rules can sometimes include exceptions for web browser processes. This is the case, for example, for a rule given by Elastic. It can therefore be interesting to use the browser process (by injecting a Cobalt Strike beacon into it, for example) to carry out ticket requests:
network where host.os.type == "windows" and event.type == "start" and network.direction : ("outgoing", "egress") and
destination.port == 88 and source.port >= 49152 and process.pid != 4 and
not process.executable :
("?:\Windows\System32\lsass.exe",
"System",
"?:\Windows\System32\svchost.exe",
"?:\Program Files\Puppet Labs\Puppet\puppet\bin\ruby.exe",
"\device\harddiskvolume?\windows\system32\lsass.exe",
"?:\Program Files\rapid7\nexpose\nse\.DLLCACHE\nseserv.exe",
"?:\Program Files (x86)\GFI\LanGuard 12 Agent\lnsscomm.exe",
"?:\Program Files (x86)\SuperScan\scanner.exe",
"?:\Program Files (x86)\Nmap\nmap.exe",
"?:\Program Files\Tenable\Nessus\nessusd.exe",
"\device\harddiskvolume?\program files (x86)\nmap\nmap.exe",
"?:\Program Files\Docker\Docker\resources\vpnkit.exe",
"?:\Program Files\Docker\Docker\resources\com.docker.vpnkit.exe",
"?:\Program Files\VMware\VMware View\Server\bin\ws_TomcatService.exe",
"?:\Program Files (x86)\DesktopCentral_Agent\bin\dcpatchscan.exe",
"\device\harddiskvolume?\program files (x86)\nmap oem\nmap.exe",
"?:\Program Files (x86)\Nmap OEM\nmap.exe",
"?:\Program Files (x86)\Zscaler\ZSATunnel\ZSATunnel.exe",
"?:\Program Files\JetBrains\PyCharm Community Edition*\bin\pycharm64.exe",
"?:\Program Files (x86)\Advanced Port Scanner\advanced_port_scanner.exe",
"?:\Program Files (x86)\nwps\NetScanTools Pro\NSTPRO.exe",
"?:\Program Files\BlackBerry\UEM\Proxy Server\bin\prunsrv.exe",
"?:\Program Files (x86)\Microsoft Silverlight\sllauncher.exe",
"?:\Windows\System32\MicrosoftEdgeCP.exe",
"?:\Windows\SystemApps\Microsoft.MicrosoftEdge_*\MicrosoftEdge.exe",
"?:\Program Files (x86)\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe",
"?:\Program Files\Google\Chrome\Application\chrome.exe",
"?:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
"?:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
"?:\Program Files\Mozilla Firefox\firefox.exe",
"?:\Program Files\Internet Explorer\iexplore.exe",
"?:\Program Files (x86)\Internet Explorer\iexplore.exe"
) and
destination.address != "127.0.0.1" and destination.address != "::1"
By combining all these options, it is possible to perform Kerberoasting while leaving a small enough number of traces to pass under the radar of detection solutions.
Detecting « OPSEC » Kerberoasting
As mentioned above, Kerberoasting can be carried out more discreetly by combining several techniques. However, it is still possible to detect these actions:
- Checking that kerberos flows originate from a legitimate process is a possible way of detecting malicious actions (although it can generate false positives, as we saw earlier);
- An interesting solution to detect kerberoast attacks (or make attackers and red team paranoid) is to use honeypots domain accounts with an SPN and a strong password (so that it cannot be broken). However, there are a few things to bear in mind if you don’t want to raise the suspicion of an attacker:
- The honeypot account must correspond to the accounts already present. For example, a LastLogon that is too old could suggest an account that is not used for anything other than a trap, and a suspicious attacker would not target this account;
- The account should appear to have interesting privileges (even if limited, or none), as an attacker will be less likely to target accounts that do not enable him to establish an exploitation scenario.
Conclusion
Many of the elements of Kerberoasting detection have been discussed in this first part. Here is a summary table:
Action | Event ID | Filter | Explanation |
LDAP query listing accounts with SPN(s) | 1644 – LDAP | servicePrincipalNames=* | An LDAP request with this filter will only occur if accounts with an SPN are listed, which should never happen outside Kerberoasting |
Large number of tickets requested in a short space of time | 4769 – Kerberos | N/A | A legitimate user will most likely never request a very large number of tickets in a very short space of time |
Encryption downgrade | 4769 – Kerberos | Ticket Encryption Type field | For accounts with AES128/AES256 algorithms enabled, watch out for RC4 ticket requests. |
Unusual or unknown ticket options | 4769 – Kerberos | Ticket Options field | The ticket options of certain tools are quickly recognizable, such as those of GetUserSPNs.py from the impacket suite |
Processes on which Kerberos traffic passes | 3 – Sysmon | Image, User, Destination Port fields | In some cases, kerberos traffic will not pass through the LSASS process |
Honeypots accounts | 4769 – Kerberos | Service Name field | Use « attractive » accounts to trick the attacker |