Order of Operations: The First Hour of a Compromised Cloud Account
Notes from writing an incident response runbook for a compromised Microsoft Entra ID account: why the order of containment steps matters, what attackers leave behind, and the decisions worth making before the incident instead of during it.
My last post was about preventing account takeover with identity controls written as code. This one picks up where prevention runs out: one of your users' accounts is compromised anyway. What do you do first?
I wrote a runbook to answer that for myself: entra-account-compromise-ir. It covers a compromised Microsoft Entra ID user account from first alert to lessons learned. It includes 12 KQL queries for Microsoft Sentinel and Log Analytics, a flow diagram, and a 90-minute tabletop exercise built around a fictional company. Each step maps to the NIST SP 800-61 Rev. 2 incident response phases, and most map to MITRE ATT&CK technique IDs.
Same honesty rule as last time: none of the queries has been run against a real workspace or tenant, and the runbook hasn't been exercised. The queries pass an offline check that they parse and only use documented tables and columns, and every claim was checked against the page it came from. That's all that's been proven so far, and the repo says so at the top.
Most compromises start small
A lot of cloud identity incidents start with a single user account. Someone types their password into a convincing fake login page. Someone gets worn down by a string of MFA prompts and taps "approve" on one they didn't start. A session token gets stolen. Or someone grants an official-looking app permission to read their mail.
None of those require an exotic exploit, and all of them can turn into something much bigger if the response is slow or out of order. The first hour matters, and a responder who has to figure out from scratch which logs to query and what to do first burns most of it.
The order is the point
The part of the runbook I spent the most time on is containment, because it's easy to do the right things in the wrong order.
Here's the trap. Revoking a user's sessions sounds like it kicks the attacker out, and it does, until they sign back in with the password they still know. Resetting the password sounds like it fixes things, but on its own it doesn't end every session that already exists. Each step leaves a gap the other closes.
So the runbook does them in this order, as close together as possible:
- Disable the account so nobody can sign in.
- Revoke sessions and refresh tokens so existing sign-ins stop renewing.
- Reset the password and force a change at next sign-in.
- Mark the user as compromised in Entra ID Protection, if you have the license tier for it, so user-risk policies kick in.
- Block the attacker's infrastructure with a Conditional Access policy if there's a clear set of IP addresses or countries, without blocking your break-glass accounts.
- Disable the user's devices if one of them may be attacker-registered or compromised.
Even then, it isn't instant. Microsoft's documentation says revocation can take a few minutes, and access tokens that were already issued stay valid until they expire, by default somewhere between 60 and 90 minutes, unless the app supports Continuous Access Evaluation. Apps that do can get revocation in near real time when the account is disabled or the password changes. That's why you do all three of disable, revoke, and reset, not just one.
Then you check your work. The last query in the repo looks for the containment actions in the audit log and for any successful sign-ins after you contained the account. If sign-ins are still succeeding, you go back and contain again.
Attackers leave things behind
Getting the attacker out of the session isn't the same as getting them out of the account. A capable attacker uses those first minutes to make sure they can come back. The runbook's eradication step goes looking for the usual leftovers:
- Inbox rules that forward mail out or hide replies, so the user doesn't notice the fraud emails going out in their name.
- OAuth consent grants that let a malicious app keep reading data. Microsoft's app consent playbook recommends disabling the app rather than deleting it, because a deleted app can come back if another user consents to it.
- MFA methods the attacker registered for themselves. You compare the list against what the user confirms is actually theirs.
- Devices, role assignments, and app credentials the attacker added.
- Tenant-level changes. A change to federation settings means this is no longer a one-account problem, and you escalate it as a tenant-wide incident.
The last step of eradication is to search for the same attacker IP addresses, apps, and inbox rule patterns across every other user. One compromised account can be one of several. Each new one goes back to containment.
Decide before the incident, not during it
The more I worked on this, the more I saw that the runbook's real value is the decisions it makes ahead of time. A few that I think matter for any security leader, regardless of the tooling:
Agree on when doubt means contain. The runbook's triage rule is simple: if you can't rule out compromise within your agreed time, treat it as real and contain. Containment can be reversed. Data loss can't. The hard part isn't the rule. It's getting leadership to agree, before anything happens, that disabling an executive's account on a suspicion is acceptable.
Get permissions sorted out ahead of time. Every containment step needs a specific admin role or permission. Finding out mid-incident that your responder can't disable an admin account, and waiting on someone who can, is a self-inflicted delay. The runbook lists the permission or least-privileged role for each step so those approvals can happen in advance.
Make sure the logs will be there. Entra keeps sign-in and audit logs for 7 days on the free tier and 30 days on P1 and P2 unless you export them. If the compromise started five weeks ago and you never exported anything, the Entra logs from the start of it are gone. The Microsoft 365 unified audit log keeps some activity longer (180 days on Purview Audit Standard), but it's a separate log, not a substitute for the Entra ones. That's a budget and architecture decision, and it gets made long before the incident.
Don't contact the user through the account you think is compromised. It sounds obvious. It's easy to forget when you're moving fast and the user's email is right there. The runbook says to ask out of band.
Write the timeline from logs, not memory. The lessons-learned step asks for a blameless review with the timeline taken from logs in UTC: first malicious activity, detection, containment, recovery. Memory compresses and flatters. Logs don't. The review should end with named owners and dates for each fix, or it was just a meeting.
What's next
The next step is the same one waiting on my hardening lab: stand up a dedicated test tenant, run the queries against real sign-in and audit data, tune the thresholds, and record what actually fires and what doesn't. I'd also like to run the tabletop with a few people and feed the gaps back into the runbook. When I've done that, I'll write up the results here, including what didn't work.
If you're responsible for identity in your organization, here's the one question I'd take from this: if one of your users' accounts were compromised this afternoon, does your team already know the order of the first three steps, and does the person on call have the access to take them?