socstories.blog

Detection methods, KQL & more

Detecting ADCS attacks

You can detect an ADCS attack by monitoring for Kerberos EventID 4768 with ‘PreAuthType’ == ’16’ (TGT based on user certificate) and ‘TicketOptions’ startswith ‘0x4080’ (hardcoded value in multiple attacker tools). PreAuthType 16 can (probably) be used as a detection method on its own, but it still needs to be tested if this gives false positive results in an environment with Windows Hello for Business activate.

Summary

ADCS, or Active Directory Certificate Services, can provide an easy way for an attacker to escalate their privileges. The technique was first described by Will Schroeder and Lee Christensen from Specterops and presented at BlackHat USA 2021. Their whitepaper “Certified Pre-Owned: Abusing Active Directory Certificate Services”, provides an in-depth explanation of which misconfigurations can be abused and how to mitigate them.

The TL;DR is quite simple: if you have misconfigured certificate templates within your environment, an attacker on a compromised hosts can probably obtain domain admin within minutes. While the attack is quite widely known, sit seems that there are limited documented ways of actually detecting these attacks. If you are only looking for an example KQL detection rule, that can be found here

Disclaimer: If anything what I say below is blatantly wrong, or if you did further research on the topic of PreAuthType values in Windows Hello for Business environments, feel free to send me an email.

ADCS ESC1: The road to detection

Before diving deeper in the topic, we need to address the high-level concept of the ADCS ESC1 attack.
As a picture says more than a thousand words, the below illustration should fit nicely.

ADCS ESC1 Explained
Source: https://www.crowe.com/cybersecurity-watch/exploiting-ad-cs-a-quick-look-at-esc1-esc8

While helping my friends from In.Security in giving their Defending Enterprises course at BlackHat USA 2023, I looked into detecting this attack. They already had a nice detection logic in place to start out with:

  • Query for Certificate Service events 4886/4887
  • Extract hostname and certificate requester (account) from these events
  • Perform Lookup of hostname to get the associated IP address of the host
  • Query for Kerberos TGT Request event 4768
  • Extract IP address, account & certificate serial number
  • Check to see if the TGT originates from the same host from which the certificate was requested, within a certain timeframe
  • For any matches, check if there is a difference between the account that made the certificate request and the account name for which the TGT was issued.

While this is a solid detection method that perfectly fits the attack flow, my main issue is that you are forced to bind the TGT events and the ADCS events based on a 24 hour-ish timeframe. A patient attacker will not get caught using this detection. This gave me a mission: find a reliable way of detecting the ADCS ESC1 attack.

PreAuthType and TicketOptions

While going through all the data available in the events related to ADCS, it became clear none of these events actually were verbose enough to identify if the requested certificate is vulnerable. While EventID 4898 is logged when a template is loaded (and contains enough information to determine if it is vulnerable), it is only logged when the template is loaded. Meaning that consecutive uses of this template will not trigger this event.
EventIDs 4886 (Certificate Services received a certificate request) and 4887 (Certificate Services approved a certificate request and issued a certificate) are logged each time, but they do not contain enough information to determine if this activity is malicious. This is a dead end, and it was time to move on to trying to detect the next step in the attack.

The next step in the attack flow is requesting a Kerberos TGT. At this point BlackHat was long gone and I was out of a detection lab to play around with. It took me a while to get everything set up, proper logging configured and linked to Sentinel (which is my SIEM of choice for everything on this blog). That entire process will be another post for another time.

Moving on to analyzing the logs: requesting a Kerberos TGT, which logs an event with ID 4768 containing the following information in EventData.

EventData for a benign EventId 4768
EventData for a malicious EventId 4768

When comparing these fields from malicious and non-malicious events, I started to realize that the fields PreAuthType and TicketOptions could be used to identify malicious events. In all the malicious events, PreAuthType was 16 and TicketOptions started with 0x4080.
So I tested this a few times, it seemed to work perfectly. However, me not being a red teamer limited my capabilties (and motivation) of performing this attack with a bunch of different tools and in different ways. Credits to the same friends at In.Security who were kind enough to open up their detection lab again at this point, and generate a bunch more traffic.

This allowed me to confirm my conclusion: ADCS ESC1 attacks can be detected at the stage of a malicious TGT request be looking at EventID 4768, where PreAuthType == 16 and TicketOptions starts with 0x4080.

The remaining question was … why? So it was time to go down the rabbit hole

TicketOptions explained

As explained in the Microsoft documentation, the TicketOptions are ‘a set of different ticket flags in hexadecimal format’.
They give the following nice example:

  • TicketOption: 0x40810010
  • Binary view: 01000000100000010000000000010000
  • Using MSB 0 bit numbering we have bit 1, 8, 15 and 27 set = Forwardable, Renewable, Canonicalize, Renewable-ok.

The documentation also contains a table defining the meaning of each of these flags. We can use this to translate the value the value 0x40800010 (which we observed in our malicious event), with the following result:

  • TicketOption: 0x40800010
  • Binary view: 01000000100000000000000000010000
  • We have flags on bits 1, 8 and 27. However, we are missing flag 15

Flag 15 indicates ‘Canonicalize’ and seems to be missing in TGT requests that originate from Rubeus. This detection method has some documentation online, including a blog on securelist.com describing this in-depth. As described there, these flags are hardcoded in the Rubeus source code on purpose.

A screenshot of Rubeus source code showing that the TicketOptions flags are set intentionally
Source: https://securelist.com/anomaly-detection-in-certificate-based-tgt-requests/110242/

I am unsure what the specific reason is for this flag being missing, and if changing this value would have an impact on any of the functionalities of the tool. Furthermore this flag is also missing in Impacket, resulting in this being a solid detection for the most popular tools. Based on my current research and what is documented online, this value would detect the default configuration of at least Rubeus, Impacket, Certipy and Whisker.
However, it is still possible to alter this value and it is a detection method for a tool instead of a technique. This is not wat I was looking for, but I consider it as a nice bonus.

PreAuthType explained

As TicketOptions turned out to be a tool fingerprint, my last hope was to take a look at why PreAuthType was different for these events.
Again I went to look up the official Microsoft documentation, which had a table explaining all the different PreAuthType values:

Source: https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768#table-5-kerberos-pre-authentication-types

The explanations for PreAuthType 15, 16 and 17 were somewhat unclear to me. Where 15 says it is used for Smart Card logon authentication, 16 seems to be the request for Smart Card authentication and 17 should also be used for Smart Card authentication, but it sometimes isn’t?

One thing is for sure: we never used a smart card at this point. Then why are we receiving this PreAuthType?

Well, let’s consider a smart card authentication flow

  • The user inserts a smart card into the smart card reader of their workstation
  • The computer prompts for a PIN code, which is sent to the smart card
  • This PIN code unlocks a section of the smart card containing the user his public key pair
  • a Public Key Infrastructure (PKI) Authentication request (PA_PK_AS_REQ) is sent from the authenticating user to the Domain Controller
  • The Domain Controller answers with a PKI authentication service response (PA_PK_AS_REP) to the authenticating user

Understanding this worfklow means that PreAuthType 16 should be sent for each smart card authentication request, which would mean that the detection method can give false positive results in an environment using smart card authentication. Preparing myself for quite the disappointment, this had to be verified.

When testing this in the lab however, we noticed some different results. Below snippet shows the EventData for a logon using smart card authentication.

EventData for an EventId 4768 in a smart card authentication scenario

As we can see here, the TGT Request logged a PreAuthType 15. On the off chance that this is a glitch, I compared some more logs and consistently see PreAuthType 15 being logged in this scenario. But then what do other people report about PreAuthType 16 you might ask?
Well, I found exactly 1 site discussing this: a comment chain on the Microsoft Documentation github. It appears that PreAuthType 16 might be a common value in Windows Hello for Business logins. However, I have been unable to test this in my lab or find any other source to confirm this.

Kerberos authentication flow

Now because we obviously can’t leave a question unanswered, I went ahead and checked the official Kerberos documentation that is linked in the documentation of EventID 4768. This article explains how the Kerberos authentication flow works, what requests are being sent and the data available in these requests. Luckily for us, requesting a TGT (and triggering EventID 4768) is literally the first steps in that authentication flow. We don’t need to care about the rest of the process.

Source: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc772815(v=ws.10)

As the above image shows, the initial message sent to the Authentication Service requesting a TGT is the KRB_AS_REQ message. This message and the KRB_AS_REP response contain the most detailed information. Subsequent messages have less detail because they contain similar fields and data.

The below table shows that this KRB_AS_REQ message contains Pre-authentication data and interestingly enough also mentions the use of smart cards.

Source: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc772815(v=ws.10)

Regardless of the goal of the Pre-authentication data, this shows that the PAData Type is either PA-AS-REQ or PA-PK-AS-REQ in these requests fields. Comparing this with the previous table with the different meanings of the PreAuthType values, this corresponds with the value 16.
In human language: according to Microsoft documentation, a TGT request contains PreAuthType 16 in case of a smart card logon. What we observed in our lab is that PreAuthType 15 gets used in the request for a smart card logon, and PreAuthType 16 in our malicious activity.

And just to make sure I was not understanding it wrong, I went ahead and checked the PaDataType.cs file in the Kerberos.NET github repo

This code shows even more clearly that values 15 and 17 should be used in a response, and not a request. This is where I hit the limit of how deep I can go into this rabbit hole. The fact that my last Google result on the topic gave me 6 hits, indicates that Google might agree with me.
It can be that Windows Hello for Business logons contain a KRB_AS_REQ message with the PAType 16, which will trigger false detections when using this indicator on its own. However, there is a high chance that there will be other differences hidden in the EventData to identify malicious events. As long as I don’t see any prove otherwise, I’m considering this a solid finding.

Detection logic

To summarize: there are 2 indicators to detect an ADCS attack scenario, which can prove to be a reliable detection method:

  • TicketOptions starting with 0x4080: fingerprints a value hard coded in multiple attacker tools
  • PreAuthType 16 indicating a (malicious?) TGT request from a user certificate

While the TicketOptions can be altered in the source code of the tool, and the PreAuthType still needs to be tested in an environment with Windows Hello for Business signins activated, the combination of these two elements results in what I think is a reliable detection method not only for ADCS ESC1, but all the ADCS attacks.

If we want to create this into a detection logic, it would be useful to correlate these events with the process that initiated the authentication. This can for instance be done based on network events, as connections for Kerberos authentication will be to destination port 88 on the Domain Controller.

Below logic can be used to create a detection rule for these events:

  • Identify malicious EventID 4768 entries, based on TicketOptions starting with 0x4080 and PreAuthType being 16
  • Identify network connections related to Kerberos authentication, based on destination port 88, and their initiating process
  • join the malicious events based source Ip address and the connection & authentication happening within a minutes time from each other.

For those of us who have all their resources in Azure and use Sentinel as a SIEM, below is an example query that you could use. The initiating processes will have some false positives (you should expect lsass to pop up), but in the lab this gave some amazing results.

Example result output:

KQL Query

let timeframe = ago(7d);
//Identifies malicious Kerberos TGT requests, based on EID4768, properties PreAuthType and TicketOptions
let maliciousTGT = SecurityEvent
| where TimeGenerated >= timeframe
| where EventID == 4768
| parse EventData with * 'TargetUserName">' TargetUserName "<" *
'TargetDomainName">' TargetDomainName "<" *
'TargetSid">' TargetSid "<" *
'ServiceName">' ServiceName "<" *
'ServiceSid">' ServiceSid "<" *
'TicketOptions">' TicketOptions "<" *
'Status">' Status "<" *
'TicketEncryptionType">' TicketEncryptionType "<" *
'PreAuthType">' PreAuthType "<" *
| parse EventData with * 'IpAddress">::ffff:'SourceIpAddress"<" *
| parse EventData with * 'CertIssuerName">' Certissuer "<" *
| where PreAuthType == 16 and TicketOptions startswith "0x4080"
| summarize by TicketRequestTime = bin(TimeGenerated,1m), TargetUserName =strcat_delim("@",TargetUserName,TargetDomainName), SourceIpAddress, Certissuer, TicketOptions, PreAuthType;
//Get kerberos connections, based on destination port 88
//Goal: Identify host computer & initiating process
let KerberosConnections = VMConnection
| where TimeGenerated > timeframe
| where DestinationPort == 88 and Direction == "outbound"
| summarize InitiatingProcessList = make_set(ProcessName) by ConnectionTime = bin(TimeGenerated,1m), SourceIp, Computer
| project ConnectionTime, InitiatingProcessList, SourceIp, Computer;
//join TGT requests and network connections, do left outer to make sure not to miss events if there are no network connections within the timeframe
maliciousTGT
| join kind=leftouter KerberosConnections on $left.SourceIpAddress == $right.SourceIp, $left.TicketRequestTime == $right.ConnectionTime
| project TicketRequestTime, SourceHost=Computer, InitiatingProcessList, TargetUserName, TicketOptions, PreAuthType

Author: