Return to overview
7 min read

Sneaky 2FA: Use This KQL Query to Stay Ahead of the Emerging Threat

7 min read
February 25, 2025
By: Bas van den Berg
SNEAKY 2FA protecting against the threat
By: Bas van den Berg
9 April 2025

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

We recently became aware of a new Attacker-in-the-Middle (AITM) phishing kit aptly named Sneaky 2FA. We quickly developed a Sentinel KQL query and performed a retroactive threat hunt at scale, searching through hundreds of Sentinel workspaces in less time it takes to grab a good cup of coffee. We did, in fact, come across Business Email Compromises (BECs) that can now be attributed to Sneaky2FA. However, all of these BECs were detected by the myriad of other detection rules that we deploy to customer Sentinel workspaces. One of these detection rules was for an AITM phishing kit named W3LL. Sources such as Sekoia suggest that Sneaky 2FA is derived from W3LL.

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

 
Detecting the presence of one of these user agents can be an effective indicator. This, however, often results in high false positive rates and may lead to alert fatigue in the SOC. At the same time, the behaviour itself creates a detection opportunity. Because we like our SOC analysts and don't like false positives, we set out to build a generic query to detect this behaviour with high fidelity.
 

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

 

results of the queryImage 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.

sneaky 2FA query results

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:

Let's talk

Curious to know how we can help?

Get in touch
GET IN TOUCH
Share this article.