In this section

M365 IR Containment: Emergency CA Policy, Session Revocation, CAE Timing, and Service Principal Rotation

Module 6

Block first, clean up second

The containment sequence from IR6.1 starts here. The emergency CA policy and session revocation are Steps 1 and 2 because they're the fastest actions that have the broadest impact. The CA policy blocks all new authentication for compromised accounts. Session revocation terminates existing sessions. Together, they create a containment window: within minutes, no new sessions can be established, and within an hour, all existing sessions are terminated. Everything after this point (password resets, application cleanup, rule removal) happens inside that window while the attacker is locked out.

The speed matters. Every minute between the containment decision and the CA policy deployment is a minute the attacker can escalate, exfiltrate, or establish new persistence. If the emergency CA policy is pre-built (Module 3), deployment takes 30 seconds. If it's built during the incident, it takes 15-20 minutes of policy configuration while the attacker continues operating.

The emergency CA policy

The emergency CA policy is a block-all policy scoped to the compromised accounts (or the entire tenant in severe cases) with exclusions only for the IR team's break-glass accounts and named investigation locations.

Entra Admin Center

Deploy the emergency CA policy:
ProtectionConditional AccessPolicies → select the pre-built emergency policy
Switch the policy from Report-only to On. If scoping to specific accounts: UsersInclude → select the compromised user accounts. Exclude the IR team's break-glass accounts. GrantBlock access.

The policy design for a multi-account BEC containment:

Users scope: Include all compromised accounts identified in Module 5. If the attacker had Global Admin or other tenant-wide administrative access, consider scoping the policy to all users except the IR team's break-glass accounts. A tenant-wide block is aggressive and disrupts legitimate users, but may be necessary if the investigation can't confirm the full scope of compromised accounts. The decision depends on the confidence of the scoping from Module 4: if you're confident you've identified all compromised accounts, scope to those accounts. If the attacker had admin access and may have compromised accounts you haven't identified, the wider scope is safer.

Cloud apps: All cloud apps. Don't scope to specific applications. The attacker may access resources through applications you haven't identified. If you scope the block to Exchange Online and SharePoint but the attacker has an OAuth app accessing Microsoft Graph, the Graph access continues. Blocking all cloud apps ensures no authentication path remains open.

Conditions: All platforms, all locations except the IR team's named location (the IP range the IR team operates from). The named location exclusion ensures the IR team can still authenticate to perform the remaining containment steps. If the IR team is working remotely from multiple locations, add all their IPs to the named location before deploying the policy. An IR team member locked out by their own emergency policy is a containment delay you can't afford.

Grant: Block access. No alternative controls (no "require MFA" fallback, no "require compliant device" alternative). The goal is complete authentication denial for the compromised accounts. Any alternative control gives the attacker a potential path through.

The policy takes effect within seconds for new authentication attempts. Any sign-in attempt by the compromised accounts from outside the IR team's named location is immediately blocked with a ConditionalAccessStatus of "failure" and ResultType 53003 in the sign-in log. The attacker sees an access denied message. They know containment has started. This is expected: the speed of containment matters more than stealth at this stage, because the attacker's existing sessions are being terminated simultaneously.

Session and token revocation

With the CA policy blocking new authentication, the next step terminates existing sessions. The attacker may have active browser sessions, Outlook connections, or API sessions that were established before the CA policy was deployed. These sessions continue working until their tokens expire unless you explicitly revoke them. The CA policy prevents new sessions but doesn't terminate existing ones. That's the revocation's job.

# CONTAINMENT: Revoke all sessions for compromised accounts
$compromisedUsers = @(
    "finance.director@yourdomain.com",
    "ap.clerk@yourdomain.com",
    "procurement.mgr@yourdomain.com",
    "cfo.assistant@yourdomain.com"
)

foreach ($user in $compromisedUsers) {
    Revoke-MgUserSignInSession -UserId $user
    Write-Host "Sessions revoked: $user at $(Get-Date -Format 'u')"
}

Revoke-MgUserSignInSession invalidates all refresh tokens for the specified user. This is a CAE critical event: CAE-capable applications (Exchange Online, SharePoint Online, Teams, Microsoft Graph) receive the revocation signal and reject the current access token within minutes, even if the access token hasn't expired. The user (and the attacker) is forced to re-authenticate, which the CA policy from Step 1 blocks.

Log the exact timestamp of each revocation. The investigation report needs the containment timestamp for each account, and the verification queries use this timestamp as the boundary between "attacker may have access" and "attacker should not have access." If the verification query shows successful sign-ins after the revocation timestamp, containment failed for that account and you need to investigate why.

Run the revocation during a period when you can monitor the results in near-real-time. Don't revoke sessions at 2 AM and check the results in the morning. Revoke during active monitoring hours so you can immediately detect and respond to any containment failures.

The CAE timing gap

CAE-capable applications respond to revocation within 1-10 minutes. Non-CAE applications don't receive the revocation signal. They continue accepting the existing access token until it expires naturally (default: approximately 1 hour, up to 28 hours for CAE-extended tokens that were issued before the revocation).

REVOCATION PROPAGATION TIMELINE

Time after revocation
Application type
Status
0-10 minutes
CAE-capable (Exchange, SharePoint, Teams, Graph)
Access terminated
10-60 minutes
Non-CAE applications (third-party SaaS, legacy)
Access continues until token expires
Indefinite
Service principal (OAuth app with own credentials)
Unaffected by user session revocation

Service principals require separate containment (IR6.4). User session revocation does not affect them.

The timing gap is the window during which the attacker can still take actions through non-CAE applications. For most M365-focused attacks, the primary applications (Exchange, SharePoint, Teams) are CAE-capable and terminate within minutes. The risk is from third-party SaaS applications connected via OAuth that don't support CAE. If the attacker is accessing a non-CAE application, you may need to disable the application's OAuth consent or block the application at the CA policy level in addition to revoking the user's sessions.

The practical response to the timing gap: don't wait passively for tokens to expire. Run the containment verification query 15 minutes after revocation, then again at 30 minutes, then at 60 minutes. If successes from the attacker's IP stop within the first 15 minutes, CAE handled the revocation. If successes continue at 30 minutes but stop by 60, a non-CAE application had an unexpired token that has now expired. If successes continue beyond 60 minutes, you have a persistence mechanism that session revocation doesn't affect (a service principal, a managed identity, or an application-level credential). Each check narrows the diagnosis.

Containment verification

After deploying the CA policy and revoking sessions, verify that the containment is working. The sign-in logs should show failed authentication attempts from the attacker's IP within minutes.

// VERIFICATION: Confirm attacker sign-ins are blocked after containment
let compromisedUsers = dynamic(["user1@yourdomain.com", "user2@yourdomain.com"]);
let containmentTime = datetime(2026-03-18T10:00:00Z);
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > containmentTime
| where UserPrincipalName in (compromisedUsers)
| summarize
    Successes = countif(ResultType == "0"),
    Failures = countif(ResultType != "0"),
    FailCodes = make_set(ResultType)
    by UserPrincipalName, IPAddress
| sort by Successes desc

After containment, you should see zero successes from the attacker's IP and failures with ResultType 53003 (blocked by Conditional Access). If you see any successes from the attacker's IP after containment, something wasn't contained. Diagnose the failure:

If the successful sign-in is from a non-CAE application with an unexpired token, wait for the token to expire (up to 1 hour) and re-check. If the success is from a service principal (check if the AppDisplayName in the log matches a service principal rather than a user application), the service principal containment from the section below is needed. If the success shows ConditionalAccessStatus "notApplied," the CA policy scope doesn't cover the application or user involved. Check the policy's application scope and user assignment.

Successes from the IR team's named location with the IR team's accounts are expected and confirm that the exclusion is working correctly. The IR team needs continued access to execute the remaining containment steps. If the IR team's access is also blocked, the named location configuration is wrong. Fix it immediately from a break-glass account.

Service principal credential rotation

User session revocation doesn't affect service principals. The attacker's malicious OAuth application ("Document Sync Pro" from the walkthrough) authenticates with its own client secret, independent of any user's session or credentials. Revoking the user's sessions and resetting their password has zero impact on the service principal.

# CONTAINMENT: Remove credentials from attacker's service principal
$appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"  # From IR5.2 analysis
$app = Get-MgApplication -Filter "AppId eq '$appId'"

# Remove all client secrets
foreach ($secret in $app.PasswordCredentials) {
    Remove-MgApplicationPassword -ApplicationId $app.Id `
        -KeyId $secret.KeyId
    Write-Host "Removed secret: $($secret.KeyId) from $($app.DisplayName)"
}

# Remove all certificates
foreach ($cert in $app.KeyCredentials) {
    Remove-MgApplicationKey -ApplicationId $app.Id `
        -KeyId $cert.KeyId
    Write-Host "Removed certificate: $($cert.KeyId) from $($app.DisplayName)"
}

# Verify no credentials remain
$app = Get-MgApplication -Filter "AppId eq '$appId'"
Write-Host "Remaining secrets: $($app.PasswordCredentials.Count)"
Write-Host "Remaining certificates: $($app.KeyCredentials.Count)"

Removing the credentials prevents the service principal from authenticating for new sessions. Existing access tokens issued to the service principal continue working until they expire (up to 1 hour). For immediate termination, also disable the service principal in Entra ID:

Entra Admin Center

Disable the malicious service principal:
IdentityApplicationsEnterprise applications → search for the app → Properties
Set Enabled for users to sign-in to No. This immediately prevents the service principal from authenticating, even with valid credentials. Combine with credential removal for complete containment.

After removing credentials and disabling the service principal, verify in ServicePrincipalSignInLogs that no new successful authentications occur from the application. Run this check at the same intervals as the user containment verification (15, 30, 60 minutes). Any continued success indicates the attacker has a second set of credentials you haven't found (they added multiple client secrets during the compromise), a second application you haven't identified (IR5.2's persistence completeness check catches these), or the service principal disable didn't propagate yet (rare, but retry if the first check shows continued access).

For applications the attacker created (rather than applications they injected credentials into), consider deleting the entire application registration rather than just removing credentials. Removing credentials prevents authentication but leaves the application object in Entra ID, which the attacker could re-credential if they regain admin access. Deleting the application removes the object entirely. Document the deletion in the investigation log before executing it, because the deletion removes the application's audit history from the admin portal (the audit log records remain in AuditLogs).

IR6.3 covers password resets and authentication method remediation for the compromised user accounts. IR6.4 covers the full application and mailbox remediation that removes all remaining persistence mechanisms.