Kerberos serves as a network authentication protocol, specifically designed to offer robust authentication for client/server applications through the utilization of secret-key cryptography. To comprehend a Kerberos attack, it's essential to grasp the authentication flow involving the domain controller. This understanding enhances visibility, facilitating a more prompt incident response. Kerberos Communication Process
Upon a user attempting to log in with their credentials, an NTLM hash is generated for the user's password. This hash is part of the NT LAN Manager suite of Microsoft security protocols, designed to ensure user authentication, integrity, and confidentiality. Subsequently, a Ticket Granting Ticket (TGT) is dispatched to the Kerberos Key Distribution Center (KDC) Server as an authentication request (AS-REQ). The domain controller then validates the user's group policies and generates a valid TGT for authentication. This TGT is transmitted back to the user's workstation in an encrypted format as an authentication response (AS-REP). When users employ their Kerberos tickets for authentication on other systems, the Privilege Attribute Certificate (PAC) can be accessed and utilized to ascertain their privilege levels. This can be achieved without necessitating queries to the domain controller for the relevant information.

There are 3 possible values for this policy:
Inactive (default): PAC validation will not be performed whatsoever.
Activated: In the event of PAC Validation failure, the PAC information is utilized, and user login is permitted.
Mandated: In the case of PAC Validation failure, the PAC information is disregarded, leading to the denial of user login.

When a user connects to specific application servers, the existing TGT ticket is transmitted to the application server for authentication, akin to an AS-REQ (Authentication Service Request). The destination server then opens and verifies the user's TGT ticket by utilizing the NTLM password hash.
Therefore, successful authentication is mutual to grant access to the respective users.
What is Kerberoasting?
Kerberoasting attacks exploit ticket-granting tickets (TGT) to solicit service principal names (SPN) within Active Directory domains. A service principal name (SPN) serves as a distinctive identifier for a service instance and is utilized by Kerberos authentication to link a service instance with a service logon account. Malicious actors leverage these ticket-granting services to carry out password cracking and extract them in plaintext. In the context of Kerberoasting, the RC4_HMAC encryption becomes susceptible to brute-force attacks.
How Do I Detect Kerberoasting Attacks?
As threat actors enumerate your environments for vulnerable service accounts, specific event IDs indicate active attacks. However, many security operations centers and SOC analysts often invest considerable time in investigating such activities. The key to accelerating the response of your L1 analyst is revealed here.
Associate event ID "4769" with vulnerable encryption types "0x17" in Kerberoasting and ticket option "0x40810000." The occurrence of event ID "4769" indicates a request for a Kerberos service ticket. Concurrently, examine the logs for the ClientIP to determine the origin of the attack.
Correlation Rule:

```C#
// create a whitelist that consists of your AD domain names. ex:
("contoso.com","contoso.local","contoso.dmz")
let _whitelist_ServiceName = dynamic (["contoso.com","contoso.local","contoso.dmz"]);
let _fromDate = ago(8d);
let _thruDate = now();
let _min_dcount_threshold = 6;
let _period =2h; // this value should also be used as the rule frequency.
// If an account requested more than <_min_dcount_threshold> service tickets during the last <_period> hour,
// we need to check if it is suspicious or not by looking at historical data for the same user.
// STEP 1: Create a list of accounts to be checked based on their requests during the last <_period> hour.
// This approach also increases the rule performance as only the suspicious accounts are checked historically.
let _accounts_to_be_checked =
SecurityEvent
| where TimeGenerated > ago(_period)
| where EventID == 4769
| parse EventData with * 'Status">' Status "<" *
| where Status == '0x0'
| parse EventData with * 'ServiceName">' ServiceName "<" *
| where ServiceName !contains "$" and ServiceName !contains "krbtgt"
| where ServiceName !in~ (_whitelist_ServiceName )
| parse EventData with * 'TargetUserName">' TargetUserName "<" *
| where TargetUserName !contains ServiceName
| parse EventData with * 'TicketEncryptionType">' TicketEncryptionType "<" *
| where TicketEncryptionType in~ ('0x17','0x18') //these are the weak encryption types.
| parse EventData with * 'TicketOptions">' TicketOptions "<" *
| parse EventData with * 'IpAddress">::ffff:' ClientIPAddress "<" *
| summarize dcount(ServiceName) by TargetUserName
| where dcount_ServiceName > _min_dcount_threshold
| summarize make_list(TargetUserName);
// STEP 2: Create a time-series statistics per day for each account in the list, for the last 8 days.
let baseData = materialize(
SecurityEvent
| where TimeGenerated > _fromDate
| where EventID == 4769
| parse EventData with * 'Status">' Status "<" *
| where Status == '0x0'
| parse EventData with * 'ServiceName">' ServiceName "<" *
| where ServiceName !contains "$" and ServiceName !contains "krbtgt"
| where ServiceName !in~ (_whitelist_ServiceName )
| parse EventData with * 'TargetUserName">' TargetUserName "<" *
| where TargetUserName in~ (_accounts_to_be_checked) // only the accounts in the list will be analyzed.
| parse EventData with * 'TicketEncryptionType">' TicketEncryptionType "<" *
| where TicketEncryptionType in~ ('0x17','0x18')
| parse EventData with * 'TicketOptions">' TicketOptions "<" *
| parse EventData with * 'IpAddress">::ffff:' ClientIPAddress "<" *
//creating the time-series stats
| make-series dcount(ServiceName) default=0 on TimeGenerated in range(_fromDate, _thruDate, 1d) by TargetUserName, ClientIPAddress
| extend avg = todouble(series_stats_dynamic(dcount_ServiceName).avg ) );
// STEP 3: Find outliers(anomalies) in the time-series data and make a list of suspicious accounts.
let AnomTargetUserNames = (baseData
| extend outliers = series_outliers(dcount_ServiceName)
| mvexpand dcount_ServiceName, TimeGenerated, outliers to typeof(double)
// dcount must be at least greater than the threshold. otherwise small numbers can also be an outlier.
// ex: 1,1,1,1,5 -> 5 is an outlier but probably not suspicious in big environment
| where dcount_ServiceName > _min_dcount_threshold
// time-series function rounds the time information and displays the time info as the startofday like '2020-08-23T00:00:00.0000000Z'.
// check if the anomlay happened on the current day.
| where TimeGenerated >= startofday(now())
// we are looking for positive outliers, meaning increase in the volume.
// this threshold can be adjusted based on the number of false positives or false negatives
| where outliers > 1.5
// we are looking for big outliers. Top 25 outliers should be more than enough.
| top 25 by outliers desc
| summarize make_list(TargetUserName) );
// STEP 4: Anomaly is found but it's not clear when it happened exactly and there is not enough information for incident response.
// Get last <_period> of data and dcount of service names, set of service names that have been requested for each account in the anomalous account list.
// Join with the baseData as it has both avg values and other details of the anomaly.
// Avg value will also be used for preventing the rule from triggering the same alert again.
// We already found the anomalous users but we need to check if the anomaly happened in the last <_period> to prevent duplicate incidents.
SecurityEvent
| where TimeGenerated >= ago(_period)
| where EventID == 4769
| parse EventData with * 'Status">' Status "<" *
| where Status == '0x0'
| parse EventData with * 'ServiceName">' ServiceName "<" *
| where ServiceName !contains "$" and ServiceName !contains "krbtgt"
| where ServiceName !in~ (_whitelist_ServiceName )
| parse EventData with * 'TargetUserName">' TargetUserName "<" *
| where TargetUserName in (AnomTargetUserNames) // checking if the user is in the list of anomolous users
| parse EventData with * 'TicketEncryptionType">' TicketEncryptionType "<" *
| where TicketEncryptionType in~ ('0x17','0x18')
| parse EventData with * 'TicketOptions">' TicketOptions "<" *
| parse EventData with * 'IpAddress">::ffff:' ClientIPAddress "<" *
// Calculate the dcount of service tickets, create the set of service tickets requested
| summarize start_Time=min(TimeGenerated), end_Time=max(TimeGenerated), ServiceNameCount_LastPeriod = dcount(ServiceName), ServiceNameSet_LastPeriod = makeset(ServiceName) by TargetUserName
// Now join this with the baseData to display the details of the anomaly.
| join kind=leftouter baseData on TargetUserName
| where ServiceNameCount_LastPeriod > avg + 2 // this is a simple check to see if the anomaly happened in the last period.
// Display the columns we need
| project start_Time, end_Time, ClientIPAddress, TargetUserName, ServiceNameCount_Avg = avg, ServiceNameCount_LastPeriod, dcount_ServiceName, ServiceNameSet_LastPeriod.


How Do I Prevent Kerberoasting?
Configure securely your Kerberos-related domain controller policies and settings.
Conclusion
A significant volume of logs will be generated during attacks such as Kerberoasting. It is crucial to employ a statistical approach and robust correlation logic for effective detection. Happy hunting!