At Eye Security, we constantly seek out new threats and detection methods to protect our customers. In the past months, we have observed a rising trend in Business Email Compromises (BECs). In this instance, threat actors use Attacker-in-the-Middle (AITM) phishing kits that lure users, via links in email messages, to phishing pages. These phishing pages are pixel-for-pixel the same as the regular Microsoft login pages. However, the users' interaction is proxied to a server controlled by the threat actor. This allows the threat actor to grab not only the username and password, but also the session token, which, in turn, bypasses multi-factor authentication (MFA). Read on and find out how to stay protected.
We protect customers not only through prevention but also by detecting and responding to suspicious signins. In order to do this at scale, we employ Microsoft Sentinel for our Microsoft 365 customers. This approach allows for tight integration with the Microsoft ecosystem, along with the flexibility to create custom rules and threat hunt at scale.
Sneaky 2FA: new threat exploits vulnerabilities in Microsoft 365 accounts with two factor authentication
In our opinion, the interesting thing about Sneaky 2FA is the way it rotates through user agents during the signin process/authentication attempt when a user is phished. Sneaky 2FA uses a set of five common and slightly outdated yet distinct user agents. Here is an example:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0
Developing a KQL query to detect an impossible device shift during signin
We wrote a generic KQL query that detects, as Sekoia dubs it, "an impossible device shift" during the authentication flow. This query can be deployed in Sentinel workspaces as a scheduled search with a short lookback window. We are gladly sharing it with the community. Our in-house version adds a few KQL tricks to further reduce the already low false positive rate. Furthermore, the jaccardIndex
parameter can be tuned to weed out false positives due to browser version updates.
Here is the KQL query in all its glory:
// Eye Security 2025
let suspiciousSignins = SigninLogs
| where DeviceDetail.deviceId == ''
| summarize userAgents = make_set(UserAgent) by CorrelationId
| where array_length(userAgents) > 2;
let correlationIds = suspiciousSignins
| mv-expand i = range(0, array_length(userAgents) - 2)
| mv-expand j = range(i + 1, array_length(userAgents) - 1)
| extend userAgent1 = tostring(userAgents[toint(i)])
| extend userAgent2 = tostring(userAgents[toint(j)])
| extend array1 = to_utf8(userAgent1)
| extend array2 = to_utf8(userAgent2)
| extend jaccardIndex = jaccard_index(array1, array2)
| where jaccardIndex < 0.8
| summarize by CorrelationId;
SigninLogs
| where ResultType == 0
| where CorrelationId in (correlationIds)
Breakdown of the query
Let's break the query down. First, the query takes the SigninLogs
where there's no Entra-registered device:
let suspiciousSignins = SigninLogs
| where DeviceDetail.deviceId == ''
We do this because a threat actor cannot have an Entra registered or joined device during the initial compromise. This reduces the false positive rate tremendously.
Then, the query takes all the UserAgents
associated with the same CorrelationId
and only keeps the rows that have three or more User Agents:
| summarize userAgents = make_set(UserAgent) by CorrelationId
| where array_length(userAgents) > 2;
Microsoft describes the CorrelationId
as follows:
Sign-in logs contain several unique identifiers that provide further insight into the sign-in attempt. Correlation ID: The correlation ID groups sign-ins from the same sign-in session. The value is based on parameters passed by a client, so Microsoft Entra ID can't guarantee its accuracy.
We indeed observe outliers with respect to the CorrelationId
. For instance, in some cases, the same CorrelationId
is attached to multiple cloud identies, which should not happen. We have yet to uncover the cause of this discrepancy. Further, this part of the query also gets results where Chrome/130
is updated to Chrome/131
. We need to filter these out to prevent false positives. This is what the next part of the query does.
Using the Jaccard index KQL function
The first part of the query yields an array of UserAgents
per CorrelationId
. We take this information and transform it so it can be fed to a KQL function called jaccard_index
. This Jaccard index can be used to quantify the similarity of two sets.
The query does this by generating all pair-wise combinations of the User Agents, first generating two helper columns:
let correlationIds = suspiciousSignins
| mv-expand i = range(0, array_length(userAgents) - 2)
| mv-expand j = range(i + 1, array_length(userAgents) - 1)
Then, it grabs the corresponding user agents from the array userAgents
:
| extend userAgent1 = tostring(userAgents[toint(i)])
| extend userAgent2 = tostring(userAgents[toint(j)])
When shown to a colleague, his response was "I would have written this for i / for j
loop differently in a SQL-like language; it works fine, but it feels like someone who wants to write C in a KQL query". Guilty as charged!
This yields two strings, but the jaccard_index
function operates on sets, so we transform the strings to an array of byte values using KQL's to_utf8
:
// create inputs for jaccard_index function
| extend array1 = to_utf8(userAgent1)
| extend array2 = to_utf8(userAgent2)
Finally, we are able to calculate the Jaccard index and filter out the values that are too similar (e.g. Chrome/130
and Chrome/131
), giving us a list of correlationIds
that we can cross-reference with the SigninLogs
:
| extend jaccardIndex = jaccard_index(array1, array2)
| where jaccardIndex < 0.8 // cut-off
| summarize by CorrelationId; // leave only suspicious correlationIds
The cut-off point of 0.8 is determined by taking the user agents that Sneaky2FA uses and calculating the corresponding Jaccard indices:
let userAgents = dynamic([
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0 OS/10.0.22635",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0"]
);
print dummy = 0
| mv-expand i = range(0, array_length(userAgents) - 2)
| mv-expand j = range(i + 1, array_length(userAgents) - 1)
| extend userAgent1 = tostring(userAgents[toint(i)])
| extend userAgent2 = tostring(userAgents[toint(j)])
| extend array1 = to_utf8(userAgent1)
| extend array2 = to_utf8(userAgent2)
| extend jaccardIndex = jaccard_index(array1, array2)
| project userAgent1, userAgent2, jaccardIndex
Image 1. Sneaky 2FA: user agent Jaccard indices
Based on these Jaccard indices, we took 0.8 as an appropriate threshold value.
Sneaky 2FA: putting it all together to combat phishing attacks
The final piece of the puzzle is taking the suspicious correlationIds
and cross-referencing these with the SigninLogs
where the authentication succeeded:
[...]
SigninLogs
| where ResultType == 0 // authentication succeeded
| where CorrelationId in (correlationIds)
This query yields a surprising fidelity already, but we've tuned it in-house to further reduce the false positive rate using knowledge that we've picked up from various internal and external research.
Here, you can see it in full swing, where it correctly identifies a Sneaky2FA login. Notice that the query correctly detects the suspicious Firefox/115
user agent without having to hardcode it in the query.
Image 2. Sneaky 2FA: displaying the query results
Conclusion
We hope this KQL query helps companies in the constant struggle against AITM phishing, especially with emerging threats such as Sneaky2FA.
To help companies in the fight against login spoofing pages, we have developed the Eye Anti-Spoofing Tool (EAST). This is our advanced cybersecurity tool especially designed for combating Microsoft login spoofing. EAST can be used a separate measure to alert users when they are entering their credentials on certain phishing pages.
FAQ
What is sneaky 2FA?
Sneaky 2FA is a sophisticated phishing kit that targets Microsoft 365 accounts, bypassing two-factor authentication (2FA) and stealing user credentials. This advanced phishing kit is sold as a Phishing-as-a-Service (PhaaS) platform on Telegram, making it accessible to a wide range of threat actors. By offering a ready-made solution, Sneaky2FA allows even less technically skilled attackers to conduct phishing campaigns without the need to develop their own phishing kit. This ease of access and use significantly increases the threat landscape, making it crucial for organisations to stay vigilant and implement robust security measures.
What are its phishing campaign tactics?
Sneaky 2FA employs a variety of tactics to evade detection and trick victims into revealing their login credentials. One notable tactic is the automatic population of the victim’s email address on the phishing page, creating the illusion of a legitimate login page. This tactic increases the likelihood of the victim entering their password, as the page appears more authentic. Additionally, the phishing kit uses adaptable code to evade detection by security tools, frequently involving Wordpress websites and other domains controlled by the attacker. These tactics make Sneaky 2FA a formidable threat, as it can easily blend in with legitimate websites and bypass traditional security measures.
What are Sneaky 2FA's evasion techniques?
To avoid detection by security tools, Sneaky 2FA employs several sophisticated evasion techniques. One such technique is redirecting security tools to Wikipedia pages, making the request appear legitimate and harmless. This redirection confuses security tools and prevents them from identifying the phishing pages. Furthermore, Sneaky 2FA uses Cloudflare’s free firewall service to block web security crawlers, adding another layer of protection against detection. These evasion techniques make it challenging for security tools to identify and block the phishing pages, allowing the threat actors to operate with relative impunity.
What mitigation strategies can be used?
To mitigate the risks posed by Sneaky 2FA, organisations should consider implementing phishing-resistant authentication methods, such as Microsoft Authenticator. This tool provides an additional layer of security, making it more difficult for attackers to bypass two-factor authentication. Additionally, real-time URL scanning and detection of newly-registered phishing domains can help prevent phishing attacks before they reach users. Educating users about the risks of phishing and how to identify legitimate websites is also crucial. By raising awareness and providing training, organisations can empower their employees to recognise and avoid phishing attempts.
How to prevent against this and similar phishing attacks?
Preventing phishing attacks requires a multi-layered approach. Organisations should consider implementing the following measures:
-
Educate users about the risks of phishing and how to identify legitimate websites.
-
Implement phishing-resistant authentication methods, such as Microsoft Authenticator, to add an extra layer of security.
-
Use real-time URL scanning to detect phishing pages before they can harm users.
-
Detect and block newly-registered phishing domains to prevent attackers from using fresh domains for their campaigns.
-
Deploy security tools that can detect impossible device shifts and other evasion techniques used by Sneaky 2FA.
By implementing these measures, organisations can significantly reduce the risk of falling victim to Sneaky 2FA and similar phishing attacks. A proactive and comprehensive approach to security is essential in the fight against phishing threats.
If you'd like to know how Eye Security can help you protect your company, reach out to us using the form below: