14.6 Token-Specific Containment

3-5 hours · Module 14

Token-Specific Containment

Token containment is more complex than standard password-reset containment. You must address: refresh tokens, access tokens (the revocation gap), OAuth application tokens, and device-bound tokens. Missing any one leaves the attacker with access.

Required role: User Administrator, Authentication Administrator, Application Administrator.


The containment sequence for token-based attacks

Step 1: Revoke all user refresh tokens.

1
2
// PowerShell
// Revoke-MgUserSignInSession -UserId "r.williams@northgateeng.com"

Blast radius: All sessions terminated. User must re-authenticate everywhere. Per-user. Verify: SigninLogs | where TimeGenerated > ago(30m) | where UserPrincipalName == "r.williams@northgateeng.com" | where IPAddress == "203.0.113.91" — zero rows.

Step 2: Reset the password. Prevents re-authentication with the old password.

Step 3: Revoke OAuth application consents. This is the step most containment procedures miss.

1
2
3
4
5
6
7
// Identify OAuth apps consented by the compromised user
// AuditLogs
// | where OperationName == "Consent to application"
// | where InitiatedBy has "r.williams"
// For each suspicious app:
// Remove via Entra ID  Enterprise Applications  [App]  Properties  Delete
// Or revoke user consent: Enterprise Applications  [App]  Users and groups  remove user

Blast radius: Removing the application affects all users who consented. Removing only the user’s consent affects only that user. Per-application or per-user.

Step 4: Address the revocation gap. After refresh token revocation, active access tokens remain valid for up to 90 minutes. Options:

Option A (if CAE is enabled): CAE-aware applications evaluate the revocation event and terminate the session within minutes. No manual action needed.

Option B (if CAE is not enabled): The 90-minute gap exists. To close it immediately: disable the user account (Set-AzureADUser -ObjectId "r.williams" -AccountEnabled $false). This forces all resource servers to reject the access token on next evaluation — typically within 5-15 minutes for Graph API and Exchange Online. Re-enable the account after the access token’s natural expiry.

Blast radius of account disable: Total access loss for the user. All sessions terminated. Per-user — severe but temporary.

Rollback: Re-enable the account. Set-AzureADUser -ObjectId "r.williams" -AccountEnabled $true. User re-authenticates with the new password.

Step 5: Check for device registrations.

1
2
3
// Attacker-registered devices
// AuditLogs | where OperationName in ("Register device", "Add registered owner")
// | where InitiatedBy has "r.williams" | where IPAddress not in corporate IPs

Remove any devices registered from non-corporate IPs during the compromise window.


Token containment verification

After all steps, verify:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Verify: no activity from any attacker IP in last 2 hours
union
    (SigninLogs | where UserPrincipalName == "r.williams@northgateeng.com"
        | where IPAddress !in ("192.0.2.10", "192.0.2.15")
        | where TimeGenerated > ago(2h)
        | project TimeGenerated, Table = "Interactive", IPAddress, ResultType),
    (AADNonInteractiveUserSignInLogs | where UserPrincipalName == "r.williams@northgateeng.com"
        | where IPAddress !in ("192.0.2.10", "192.0.2.15")
        | where TimeGenerated > ago(2h)
        | project TimeGenerated, Table = "NonInteractive", IPAddress, ResultType)
| order by TimeGenerated desc

Expected: Zero rows from non-corporate IPs. If results appear: a persistence mechanism was missed. Investigate OAuth apps and device registrations again.

Subsection artifact: The 5-step token containment sequence with verification. This is the containment section of your token investigation playbook — more comprehensive than the M11 containment because it explicitly addresses OAuth persistence and the revocation gap.

Compliance mapping: NIST CSF RS.MI-1 (Incidents are contained). ISO 27001 A.5.26 (Response to incidents).


Knowledge check


Emergency vs standard token revocation

Standard revocation: Revoke-MgUserSignInSession. Invalidates all refresh tokens. The revocation gap (up to 90 minutes) exists — the current access token remains valid until natural expiry.

Emergency revocation (close the gap immediately):

Step 1: Revoke tokens (same as standard). Step 2: Disable the account: Set-AzureADUser -ObjectId "[USER-OBJECT-ID]" -AccountEnabled $false. Step 3: Wait 5-15 minutes. Resource servers evaluate the disabled status and reject the access token. Step 4: Re-enable the account: Set-AzureADUser -ObjectId "[USER-OBJECT-ID]" -AccountEnabled $true. Step 5: The user re-authenticates with the new password.

When to use emergency revocation: When the attacker is actively exfiltrating data (visible in the Livestream), when the attacker has admin privileges, or when the compromised account has access to highly sensitive resources where 90 minutes of continued access is unacceptable.

When standard revocation is sufficient: The attacker’s activity is historical (not active right now), the compromised account has normal user privileges, and the data exposure risk during the 90-minute gap is acceptable.

1
2
3
4
5
6
// Verify emergency revocation: account disabled, all sign-ins failing
SigninLogs
| where TimeGenerated > ago(30m)
| where UserPrincipalName == "r.williams@northgateeng.com"
| where ResultType == "50057"  // User account is disabled
| project TimeGenerated, ResultType, ResultDescription, IPAddress

Any sign-in attempt after account disable shows ResultType 50057. This confirms the account is locked — including the attacker’s access token being rejected on its next resource request.

Try it yourself

In your test tenant: sign into Outlook Web Access in a browser tab. In a separate session (PowerShell or Entra portal): revoke the user's tokens AND disable the account. Observe: does the OWA session continue working? How long before it stops? Re-enable the account and re-authenticate. This demonstrates the revocation gap (OWA continues briefly after revocation) and the emergency closure (OWA stops within minutes of account disable).

What you should observe

After token revocation only: OWA continues for several minutes (the access token is still valid). After account disable: OWA stops within 5-15 minutes (the resource server evaluates the disabled status). This is the practical difference between standard and emergency revocation.

Check your understanding

1. You revoke tokens and reset the password. Non-interactive sign-ins from the attacker IP continue for 45 minutes, then stop. What happened?

The revocation gap. The refresh token was revoked but the active access token (issued before revocation) remained valid until its natural expiry ~45 minutes later. After expiry, the application tried to use the revoked refresh token to get a new access token — and failed. The containment worked, but with a 45-minute delay. CAE strict mode would have closed this gap to minutes. For immediate closure: temporarily disable the account.
The revocation failed for 45 minutes
The attacker re-authenticated during the gap
A different token was in use