Defending and Preventing Against Active Directory Kerberos Attacks

0

Kerberos is a network authentication protocol. It is designed to provide strong authentication for client/server applications by using secret-key cryptography. To understand the Kerberos attack, you must know the authentication flow with the domain controller for better understanding and visibility for faster incident response.


Kerberos Communication Process

  • Whenever the user tries to login with his/her username and password, NTLM hash ( NT LAN Manager is a suite of Microsoft security protocols intended to provide authentication, integrity, and confidentiality to users. ) will be created for the user’s password, TGT ( Ticket Granting Ticket ) will be sent to KDC Server ( Kerberos Key Distribution Center ) as authentication request ( AS-REQ )
  • Domain controller validates the user’s group policies and creates a valid TGT for the authentication.
  • TGT is sent back to the user’s workstation in encrypted format as an authentication response ( AS-REP )
  • When users use their Kerberos tickets to authenticate to other systems, the PAC ( Privilege Attribute Certificate ) can be read and used to determine their level of privileges without reaching out to the domain controller to query for that information.
There are 3 possible values for this policy:
Disabled (default): NO PAC validation will be done at all.
Enabled: If PAC Validation fails, the PAC information is used and the user login is allowed.
Enforced: If PAC Validation fails, the PAC information is discarded and the user login is denied.
  • Whenever user connect to other specific application servers , present TGT ticket is sent to application server to authenticate as ( AS-REQ ) , Destination server open and validate the users TGT tickets using the NTLM password hash.
  • Therefore successful authentication happens mutual to establish a access for relevant users.

What is Kerberoasting?

Kerberoasting attacks abuse the TGT ( ticket-granting tickets ) to request SPN ( service principal name ) within Active Directory domains. Service principal name (SPN) is a unique identifier of a service instance. SPNs are used by Kerberos authentication to associate a service instance with a service logon account. Threat actors use these ticket-granting services to crack passwords and retrieve them on plaintext.In Kerberoasting, encryption RC4_HMAC is susceptible to brute-force attacks.

How Do I Detect Kerberoasting Attacks?

When a threat actors enumerates your environments for vulnerable service accounts, specific event IDs show that attacks are actively happening, But most of the security operations center and soc analyst will spend more time in investigating such activities. How you can fasten your L1 analyst to respond quicker ,Secret is here.

  • Correlate the event ID “4769” with the vulnerable encryption “0x17” types in Kerberoasting and ticket option 0x40810000.
  • Event ID “4769” says Kerberos service ticket was requested, parallel Check for ClientIP in the logs Where the attack is originated.

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.

Also Read: Magecart Attack – Incident Investigation and The Key Takeaways

Conclusion

There will be an enormous amount of logs will be generated on such attacks like Kerberoastig, Its important to have a statistical approach and good correlation logic to detect such attacks. Happy hunting !!!


Previous articleJARM Tool – TLS Fingerprint to Detect Malware C2 Proactively
Next articleRansomware Attack: Incident Response Plan and Action Items
Balaganesh is a Incident Responder. Certified Ethical Hacker, Penetration Tester, Security blogger, Founder & Author of Soc Investigation.

LEAVE A REPLY

Please enter your comment!
Please enter your name here