Why using a FIDO2 security key is important
Microsoft offers a great variety of options to use as your primary authentication method, when signing-in with your Azure AD identity using a browser.
- Username and Password
- Phonenumber and SMS
- Username and Passwordless phone sign-in
- Certificate-based authentication
- Windows Hello for Business
- FIDO2 security keys
For multi-factor authentication you can use any of the following methods.
- TOTP Authenticator App
- Microsoft Authenticator app
Please note that for Passwordless phone sign-in, Certificate-based authentication, Windows Hello for Business and FIDO2 security keys you cannot enforce a second factor since those methods are considered strong authentication methods.
SMS sign-in on the other hand is currently not supported with the requirement for a second factor.
It is very important to know that only three methods mentioned protect your users against phishing attacks.
- Certificate-based authentication
- Windows Hello for Business
- FIDO2 security keys
Why this is the case is best explored using one of the freely available phishing toolkits. As always, seeing is believing and I always learn best when tinkering myself.
This is the official description on the evilginx2 GitHub page. Together with Modlishka it was one of the first, easy to use reverse proxies, that demonstrated that a second factor alone does not protect the user from being phished.
Both projects do not attempt to fool the user with a website that looks almost like the original login website, they use reverse proxy techniques to forward the actual login website (e.g. login.microsoft[.]com) to the user. The phishing website is hosted on some domain the attacker has complete control over, so it is easy to get a valid certificate. evilnginx2 even automates this process using the Let’s Encrypt certificate service.
The attacker now sits between the target service (Azure AD) and the user and has complete access to the unencrypted data flowing through the proxy.
Instead of only harvesting the data from the POST requests, e.g. username and password, you can configure evilnginx2 phishlets to also safe the session cookies. Depending on the lifetime of the session cookie, the attackers now has multiple days or just a few hours to use this information to impersonate the user. Since the initial authentication was done with a second factor, the attacker is not requested for this information a second time.
email@example.com has received an email containing a phishing link with a convincing lure. Bob thinks he is accessing a OneDrive document.
The phishing domain used is
portal.demo.nottrustedbutlegit[.]de which has no resemblance to
login.microsoft[.]com. A real attacker would most likely choose something a bit closer to the original domain.
The attacker has configured evilnginx2 to capture the session cookies as well as username and password whenever possible.
The Azure AD administrator has created a conditional access policy that requires Bob to use MFA whenever he logs into any Cloud App.
Also to protect her users she has set the session control “Persistent browser session” to “Never persistent”. This prevents the browser to save the session cookie and forces a new sign-in every time the browser is started.
Sign-in using username, password and 2FA
Bob clicks on the link and is requested to login to Azure AD. He enters his username and password and receives and pop-up on the Microsoft Authenticator app.
Since he expects this, he approves the sign-in and is successfully logged in. But instead of the document he is redirected to www.office.com.
The attacker not only has captured the username and password but also the session cookie. Using the command
sessions she can inspect all this information and even copy out the session cookie for future use.
Passwordless phone sign-in
In this scenario bob clicks the link, but has configured the Microsoft Authenticator App as a passwordless method.
The attacker was “only” able to capture the username and the session cookie. Read on to see why this makes no real difference.
Sign-in using FIDO2 security keys
Bob is using an external FIDO2 security key or the Windows built-in (>1903) FIDO capability of Windows Hello as a secure way to authenticate.
Bob is unable to use the FIDO2 security key to login to the phishing website.
On the regular website of Microsoft (login.microsoft[.]com) he would be able to pick the credential he wants to use, but not on the fake domain the attacker is using.
Because of this, the attacker is not able to capture anything.
What’s the difference?
FIDO2/WebAuthn is using different methods to prevent phishing of credentials. In this attack a very simple, but super effective technique is used to prevent the user to sign-in.
The WebAuthn Client (the browser) compares the domain name with the Relying Party Identifier (RP ID) of the public keys in the FIDO2 security key. If a domain string matches it can be used as a method to sign-in. Otherwise the user is unable to use the credential stored in the key.
And since a machine is much better in comparing a string value to another string value, even a “good” phishing attack using a IDN domain that looks exactly like the target domain to the human eye is unable to bypass this protection.
Or could you easily sport the difference between
lοɡin.miсrοsoft.com. Not that easy, ain’t it?
The second one would read in punycode: xn–lin-9tb36o.xn–mirsoft-bpf28i.com.
On Bobs FIDO2 security there are two entries:
|Relying Party Identifier||Username|
Only if the RP ID of the website is exactly the same as the domain name Bob can use his credentials to sign-in.
If you want more informations on FIDO2 and Windows Hello for Business, I recommend to watch the Ignite Session From Strong to Stronger: Phishing Resistant authentication methods (The Blueprint Files). Inbar Cizer Kobrinsky and Tarek Dawoud do a great job explaining the concept and techniques.
As show with the first two methods, the attacker was able to retrieve multiple parts of the credentials. The username, the password and the important parts of the session cookie (
Since the administrator of this tenant has put a conditional access policy in place to require MFA for each sign-in the username and password is not enough to take over the account.
But the session cookie contains all the information needed to authenticate the user and since the original session already presented a valid second factor or was using a strong authentication method like phone sign-in the attacker can easily use this cookie.
With the right browser extension and evilnginx2 this is as easy as copy, paste and visit office.com.
As you can see in this demo, the attacker not only has access to the website the victim was visiting, but any service accessible to the Azure AD identity.
The attacker can even visit aka.ms/mysecurityinfo and register a FIDO2 security key as a new login method. Since this counts as strong authentication method, the attacker does not need to know the password of the user to login to the account from now on. And even a password change will not prevent the attacker from signing in.
That the administrator has configured “Persistent browser session” to “Never persistent” has little meaning in this case, evilnginx2 does not honor the “Expires when the browsing session ends” information. The only difference this settings makes, it prevents the attacker from using the cookie after 24 hours.
If the attacked users has the ability join a device to AAD or even enroll a new device to Intune the attacker could even get a compliant “company” device and now would be able to bypass security implementation based on device compliance. This is an attack Microsoft itself has documented in the blog post Evolved phishing: Device registration trick adds to phishers’ toolbox for victims without MFA.
Conclusion, mitigation and prevention
Even if attackers can bypass some MFA methods with such an attack, MFA is still an important part of securing your identities. Based on the Cyber Signals report released by Microsoft in February of 2022 only 22% of Azure Active Directory identites use MFA. So currently convenience attackers will target those 78% of users who do not use MFA at all, while more targeted attacks might try to use advanced methods described here.
But this low number also mean that 78% of people can start with a better MFA option than out of band SMS!
- SMS based 2FA is considered bad for a long time, because of attacks like SIM swapping
- App based 2FA only based on TOTP can not provide additional information, but definitely use it if non of the following options is available
- App based 2FA with Microsoft Authenticator is the best app-based option for Microsoft customers, since the developers can integrate new capabilities like additional context or GPS based location fencing
- FIDO2 is the gold standard of 2FA methods.
It protects the user from signing-in to the wrong website, never sends credentials over the internet, has hardware backed cryptographic keys and needs real human interaction to work.
And the best thing is, you most likely have it already built into your device with Windows Hello. The FIDO Alliance recently published a new whitepaper presenting the next steps planned like using any smartphone as a FIDO key using Bluetooth or FIDO credential roaming.
But even if you cannot rollout strong MFA over night, Azure Active Directory as an IDP has more options to protect your users than MFA only. Device compliance is also a great tool to prevent unauthorized access.
Let’s see what you can do today:
Use FIDO2 security keys
The best way to mitigate this kind of phishing attacks is going passwordless with FIDO2 security keys.
You will find my blog series on going passwordless here.
Since you most likely will be unable to use FIDO2 for all accounts initially, you still can mitigate those attacks with conditional access and other methods.
Require device to be marked as compliant or Hybrid Azure AD joined device
Since those access controls rely on additional telemetry from Intune and/or on a valid device certificate on the client device this type of phishing attack is prevented completely.
The attackers proxy cannot present or relay the device certificate of the victim and therefore cannot sign-in the user successfully.
Using the new “Cross-tenant access settings” you can extend this requirement to guest accounts from trusted companies. This feature allows you to trust the home tenant of the user to handle MFA and relay device compliance or hybrid Azure AD joined device states to your tenant.
Conditional Access Session Controls
Setting the conditional access setting “Sign-in frequency” to a shorter time will not prevent the attack itself, but will limit the time window which the attacker can use the phished session cookie. This session control should only be applied when accessing resources from unmanaged or shared devices. Otherwise you risk to many Sign-In requests and angry users.
Check out the documentation before you configure this.
In my testing, if the user choose to stay signed-in or if the session setting “Persistent browser session” is set to “Always persistent”, the session cookie is valid for 90 days.
If you use “Persistent browser session” set to “Never persistent” the cookie is only valid for 24h hours and the browser will not store it after it’s closed.
You should apply this restriction to all administrative accounts.
Enable number matching
To avoid that the victim only has to click “Verify” in the authenticator app, number matching requires the user to enter a number displayed on the computer screen. This does not prevent the attack, but together with “additional context” it can make the user guess twice before clicking through.
Enable additional context in Microsoft Authenticator notifications
Additional context adds a map of the rough location, based in the ip address of the client, to the notification in the Microsoft Authenticator app. Combined with number matching this helps the user to identify poorly executed phishing attacks.
Can you spot the difference between the evilnginx2 phishing attack and the normal sign-in?
Since the public ip address of the initiating client is used, this can cause confusion when using something like a central VPN oder proxy solution or when using VDI solutions like Azure virtual Desktop with a internet breakout in a Azure datacenter.
I wrote “poorly executed phishing attacks”, this is because if you have an adversary that has the resources, they could deploy the phishing server somewhere near the target to avoid easy detection.
Require MFA when registering security information or additional devices
Sadly this conditional access policy does not add additional protection when using the “Require multi-factor authentication” grant control.
When the initial sign-in was made with MFA the user will not be re-prompted for MFA again.
Hopefully this will change in the future.
Until then you could try to limit registering security information to company devices, which would make onboarding difficult if you have front line workers without a company owned device.
For the user action “Register or join devices” there is only the “Require multi-factor authentication” option available.
If you suspect that a user is compromised, the first step should be to revoke all active sessions, check for newly added 2FA options and change the password.
The attacker can no longer use the phished cookies afterwards, without reauthentication.
Additional methods and information
Joe Stocker @ITguySoCal has published a great blog post on this topic a few years ago, covering additional defenses like using trusted locations (IP based access control), Exchange Online Client Access Rules or the use of . The custom branding problem that evilnginx2 had back then has been solved in the meantime and should not be considered a protection.
You can use KQL to hunt for possible malicious activity.
Possible illegitimate addition of a security method
Search for all occurrences, were a user has initially logged into with one IP address and then registered a new security method from another ip address. You can change the maximum amount of time difference between those events, to minimize noise.
let SecurityInfoRegistered = AuditLogs | where OperationName == "User registered security info" | extend UserPrincipalName = tostring(TargetResources.userPrincipalName) | extend IPAddress = tostring(InitiatedBy.user.ipAddress) | project TimeGenerated, UserPrincipalName, OperationName, ResultDescription, IPAddress; SigninLogs | where ResultType == 0 | mv-expand todynamic(AuthenticationDetails) | extend authenticationMethod = tostring(AuthenticationDetails.authenticationMethod) | where authenticationMethod != "Previously satisfied" | join kind=inner SecurityInfoRegistered on UserPrincipalName | project-rename PossibleAttackerIPAddress = IPAddress1, SecurityInfoTimeGenerated = TimeGenerated1, SecurityInfoResultDescription = ResultDescription1, InitialLoginMethod = authenticationMethod | extend TimeDifference = datetime_diff('second',TimeGenerated,SecurityInfoTimeGenerated) | where TimeDifference < 0 and TimeDifference > -86400 | project TimeGenerated, TimeDifference, UserPrincipalName, OperationName, InitialLoginMethod, SecurityInfoResultDescription, IPAddress, PossibleAttackerIPAddress, SecurityInfoTimeGenerated | sort by TimeGenerated
Administrators not using FIDO2 or WHfB
Identify every administrative account using a sign-in method other than FIDO2 or Windows Hello for Business (WHfB). You will need a conditional access policy that only applies to your administrative accounts to use this query.
I recommend to use the directory roles feature in conditional access to create such a conditional access policy.
let ConditionalAccessDisplayName = "Require MFA for administrators"; union SigninLogs, AADNonInteractiveUserSignInLogs | where ResultType == 0 | mv-expand todynamic(AuthenticationDetails) | extend authenticationMethod = tostring(AuthenticationDetails.authenticationMethod) | where authenticationMethod !in ("FIDO2 security key","Previously satisfied","Windows Hello for Business") | mv-expand ConditionalAccessPolicies_dynamic | where ConditionalAccessPolicies_dynamic.displayName == ConditionalAccessDisplayName and ConditionalAccessPolicies_dynamic.result != "notApplied"
evilginx2 and the evilginx2 logo is made by Kuba Gretzky (@mrgretzky) and it’s released under GPL3 license.