In our offensive engagements we often utilise various credential-based attacks and in this defensive piece, we’re going to look at a one method of aiding the detection of Pass the Ticket (PtT) attacks.
As far as we’re aware detection methods seem to be few and far between, some requiring client-side scripts, others command execution. This could be a mammoth challenge in a medium – large organisation, so the approach we’ve taken in this article is based on anomaly detection.
There are caveats with this approach…
- It is assumed each user in the environment is assigned an individual device (shared devices quickly break the underlying logic)
- The logic is based on the logging of a Kerberos Service Ticket event (ID 4769), so if services aren’t requested from the account, events won’t be logged and therefore will not be evaluated by the query
Collection of the following events is recommended, with 4769 being of particular interest in this instance.
We’re using Microsoft Sentinel for this example, hence KQL will be used in the post.
Starting off we look for Kerberos service ticket requests.
SecurityEvent | where EventID == 4769
The TargetUserName and originating IpAddress will be extracted from the EventData field included within the 4769 event (shown below).
| parse EventData with * 'TargetUserName">' TargetUserName "<" * 'IpAddress">' IpAddress "<" *
We’re not interested in capturing computer objects.
| where TargetUserName !contains '[email protected]' | where TargetUserName !endswith '$'
We then create a list of users based on unique IpAddress.
| summarize PotentialPtTEvents=make_set(TargetUserName) by IpAddress
The resulting query now resembles the following.
This logic can be expanded to capture events within a 60-minute (or any other preferred) time period.
| summarize PotentialPtTEvents=make_set(TargetUserName) by IpAddress, bin(TimeGenerated, 60min)
Finally, we check for buckets that include two or more unique usernames.
| where array_length(PotentialPtTEvents) >= 2 | sort by array_length(PotentialPtTEvents)
If we assume that a user jsmith is assigned a dedicated device, we should only see associated ticket events for this user from the IpAddress associated with this device.
The completed query and corresponding example results from our lab environment show two accounts (jsmith and smorrison) with activity associated with 192.168.2.6. This might be something we wish to dig into a little further to see if it’s expected or potentially malicious behaviour.
SecurityEvent | where EventID == 4769 | parse EventData with * 'TargetUserName">' TargetUserName "<" * 'IpAddress">' IpAddress "<" * | where TargetUserName !contains '[email protected]' | where TargetUserName !endswith '$' | summarize PotentialPtTEvents=make_set(TargetUserName) by IpAddress, bin(TimeGenerated, 60min) | where array_length(PotentialPtTEvents) >= 2 | sort by array_length(PotentialPtTEvents)
There’s further work that could be done here, perhaps even cross-referencing these with user logon events to get a true record on who is authenticated to each device at any time.
If this has you interested, why not check out some of our other blue orientated posts below.