Mehmet Ergene
Detecting AiTM Phishing and other ATO Attacks
Cloud account takeover(ATO) is an attack where attackers gain access to cloud identities by using methods like social engineering, device code phishing, adversary in the middle(AiTM), etc. Detecting these attacks can sometimes be difficult. In this blog, I’ll explain how we can develop a generic detection that covers almost any, if not all, methods for Entra ID (Azure AD). The method can be applied to other identity providers, too.
Anatomy of Acount Takeover Attacks
In almost all scenarios where a victim user involved, here is what happens in order:
0: the time when the attack is executed
t: timespan
- A victim is performing regular daily tasks on the computer (0-t)
- In some way, the victim gets tricked into signing in somewhere and the credentials get stolen (0)
- The victim continues working on the computer(0+t)
- The attacker signs in with the stolen credentials and starts performing malicious activities(0+t)
Detection
If you are doing DFIR, you can just create a time series chart by IP address for the examined user and easily see when the attacker entered the scene and stole the credentials.
When it comes to detecting such attacks, we usually tend to develop a logic like “when a user signs in from an IP address that has not been observed in the last X days, generate an alert”. While it is possible to detect the attacks, this logic can easily generate lots of false positives.
When it comes to detecting such attacks, we usually tend to develop a logic like “when a user signs in from an IP address that has not been observed in the last X days, generate an alert”. While it is possible to detect the attacks, this logic can easily generate lots of false positives.
If we look at the anatomy of the attack, we can see that both the victim and the attacker coexist in a certain time period. Changing the logic to “if a user signs in from an IP address that has not been observed in the last X days AND the sign-in happens in close proximity of the user’s latest sign-in time, generate an alert” can reduce many false positives and increase confidence.
To develop this logic, we shouldn’t look at just the SigninLogs as it contains only interactive sign-ins which don’t happen quite often. During regular user activity, lots of applications perform non-interactive sign-ins. Therefore, we need to combine both interactive and non-interactive sign-in logs to detect ATO attacks.
An easy way to develop the logic is as follows:
To develop this logic, we shouldn’t look at just the SigninLogs as it contains only interactive sign-ins which don’t happen quite often. During regular user activity, lots of applications perform non-interactive sign-ins. Therefore, we need to combine both interactive and non-interactive sign-in logs to detect ATO attacks.
An easy way to develop the logic is as follows:
- Count distinct IP addresses of a user for the last X hours
- Filter the results where the distinct IP count is greater than 1. This becomes our suspicious user list. We need to compare previous sign-ins and find which IP is suspicious.
- Get the earliest timestamp of sign-ins by the user and IP address for the last 15d.
- Join/lookup the result with suspicious users.
- Filter the result where the timestamp is greater than X hours (specified in step 1.)
Empty space, drag to resize
The query below implements this logic using SigninLogs and AADNonInteractiveUserSignInLogs. You can also grab it on my GitHub repo
// Author : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
//
// Description : Detect if a user signs in from an IP address that has not been observed in the last X days AND the sign-in happens in close proximity of the user's latest sign-in time. It's an indication of account takeover
//
// Query parameters:
//
let query_period = 3h; // change it according to your needs
let look_back = 14d;
let SuspiciousUPNs =
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(query_period)
| summarize dcount(IPAddress) by UserPrincipalName
| where dcount_IPAddress > 1
;
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(look_back)
| where UserPrincipalName in (SuspiciousUPNs)
| summarize arg_min(TimeGenerated, *), LastSeen = max(TimeGenerated), count(), SigninTypes = make_set(Category), AppsUsed = make_set(AppDisplayName), AppCount = dcount(AppId) by UserPrincipalName, IPAddress
| lookup kind=leftouter SuspiciousUPNs on UserPrincipalName
| where dcount_IPAddress > 1
| where TimeGenerated > ago(query_period)
| project-reorder TimeGenerated, LastSeen, UserPrincipalName, dcount_IPAddress, AppCount, AppsUsed, SigninTypes
Empty space, drag to resize
Happy hunting!
Share
Copyright © 2024
Featured Links
Subscribe to our Newsletter!
Thank you!
What's New
You can now edit your profile and update your preferences!