How to build an identity control plane that a modern adversary cannot phish, socially engineer, or replay — and how to take it back in the right order when they get in anyway.
Who needs this: CISO · IAM lead · IT service desk manager · Cloud platform owner · SOC lead · Incident Commander | Read time: 30 min | Maps to: CSF 2.0 PROTECT (PR.AA), DETECT (DE.CM), RESPOND (RS.MI); CIS Controls v8.1 — 5 (Account Management), 6 (Access Control Management), 8 (Audit Log Management); ISO/IEC 27001:2022 A.8.15, A.8.16
Cyber-survivors, take a seat. This is the chapter that pays for the book.
Here is the state of play. Sophos found that 79% of ransomware attacks began with an identity-based approach, and that 67% of victims confirmed the ransomware incident overlapped with an identity attack — despite 97% of those organizations having some MFA deployed, just not consistently across VPNs, firewalls and legacy apps (Sophos, State of Ransomware 2026). CrowdStrike reports 82% of its detections were malware-free (CrowdStrike 2026 Global Threat Report). Microsoft reports that 97% of identity attacks are password attacks, and — the number to write on the whiteboard — that phishing-resistant MFA blocks over 99% of identity-based attacks even when the attacker already holds a valid username and password (Microsoft Digital Defense Report 2025).
Honesty first, because you will get asked about this in a board meeting: the two big datasets disagree at the headline. Verizon's 2026 DBIR reports vulnerability exploitation at 31% overtaking credential abuse at 13% as the top initial vector, the first change in nineteen years (SecurityWeek on DBIR 2026). That is not a contradiction, it is a population difference. DBIR counts all breaches and is dominated by mass edge-device exploitation. Sophos, Coveware and Mandiant count hands-on-keyboard incident response, which is dominated by identity. Both are true. Patch the perimeter (Chapter 10) and defend the identity plane (this chapter), and stop arguing about which one is number one.
The thing that actually changed is speed. Mandiant measured the median hand-off from initial-access broker to ransomware operator at 22 seconds, down from over eight hours in 2022 (M-Trends 2026). There is no longer a window between "someone stole a credential" and "someone is inside your environment doing damage." Which means your identity controls are not a compliance exercise with a quarterly review cycle. They are the load-bearing wall.
The old perimeter was a place. The new one is a decision: should this principal, on this device, in this context, be allowed to do this thing right now? Everything in this chapter is either making that decision correctly, proving you made it, or reversing it fast when you got it wrong. Three consequences follow, and each breaks a habit most programs still have.
Your identity population is not your headcount. It is your headcount plus service principals, app registrations, workload identities, CI publishing tokens, Kubernetes service-account tokens, every OAuth grant an employee clicked through, and — new for 2026 — every autonomous agent you have deployed. Most organizations can count the first number and not the rest.
Your containment primitive is token revocation, not password reset. Microsoft says it about as plainly as a vendor ever says anything: for consented OAuth applications, "normal remediation steps (for example, resetting passwords or requiring multifactor authentication (MFA)) aren't effective against this type of attack, because these apps are external to the organization" (Detect and remediate illicit consent grants). Section 10 is the whole procedure.
Your service desk is an attack surface with a phone number. CISA's Scattered Spider advisory documents helpdesk impersonation as a primary technique — research the employee, call IT posing as them, obtain a password reset and an MFA token transfer to an attacker-controlled device, often splitting the request across separate contacts so no single agent sees the whole thing (CISA AA23-320A). Section 9 gives you a script.
Actionable takeaway: Before you buy anything, produce one number — the total count of identities in your environment, human and non-human, with an owner named for each. If you cannot produce it in a week, that gap is your first project, not your third.
Push-notification MFA asks a tired human to make a security decision at 2am, and attackers know it — that is the entire business model of MFA fatigue. CISA lists push bombing and SIM swap among Scattered Spider's confirmed techniques, alongside forging MFA credentials post-compromise (CISA AA23-320A). Every approve prompt is a coin flip you are letting someone else call.
But fatigue is the easy failure. The hard one is adversary-in-the-middle. AiTM reverse-proxy kits — Tycoon 2FA, Evilginx2, Modlishka, Muraena — sit between the victim's browser and the real identity provider and capture the session token after the victim completes genuine MFA (Group-IB; Proofpoint). Nothing is bypassed. The MFA works perfectly. It is simply irrelevant, because the attacker did not want your second factor — they wanted the cookie you got for passing it. The infrastructure is disposable by design, with rotating hosts and short domain lifetimes, so blocklist-based defense fails structurally, not occasionally.
And note what CISA is explicit about: number matching is a push-fatigue mitigation, not phishing-resistant MFA (CISA phishing-resistant MFA resources). Number matching is a speed bump on the road to the destination. Do not let anyone in your organization report it as arrival.
The property that matters is origin binding. In a FIDO2/WebAuthn registration, the authenticator generates a key pair scoped to a specific relying-party identifier — your real domain. At sign-in, the authenticator signs a challenge together with the origin the browser actually connected to. If the browser is talking to login.micros0ft-sso.com, the authenticator either has no credential for that origin or produces a signature bound to it, and the real identity provider rejects it. The user cannot be tricked into approving the wrong thing, because approval is not a human judgement about a screen — it is a machine assertion about a domain. The private key never leaves the authenticator, so there is nothing in the phishing proxy's hands worth replaying.
That is why Microsoft's ">99% of identity-based attacks blocked even when the attacker holds valid credentials" figure is credible rather than marketing. The attacker's whole toolkit — sprayed passwords, fatigue prompts, proxy pages — operates on a channel the protocol simply does not use.
CISA's own mitigation list for Scattered Spider names it precisely: phishing-resistant MFA (FIDO/WebAuthn or PKI) (CISA AA23-320A).
Passkeys come in two shapes, and treating them as one thing is the mistake of the year. A device-bound passkey has a private key that is generated on and never leaves a hardware authenticator — a security key, a TPM, a secure enclave. A synced passkey has a private key that is replicated through a cloud credential store so it lands on all of a user's devices. Both are phishing-resistant at the protocol level. They have different recovery models, different blast radii, and different answers to the question "who else can reach this key material?" A synced passkey's security floor is the security of the cloud account holding the sync store and the account-recovery path attached to it — which is, once again, a help desk with a phone number.
The control response does not depend on resolving that. For privileged roles, require attested, device-bound authenticators and do not accept a synced credential. For the general workforce, synced passkeys are an enormous improvement over passwords and push, and you should ship them. Two tiers, one policy document.
The order matters here, and the reason is boring and correct: if you enforce before you enrol, you lock out your own administrators, and the emergency you create is indistinguishable from the one you were defending against.
| # | Action | Who | Done when | Evidence to capture |
|---|---|---|---|---|
| 1 | Enumerate every account holding a privileged role, including cloud, SaaS, network, hypervisor and backup consoles | IAM lead | A signed list exists with an owner per account | Export of role assignments per platform, dated |
| 2 | Create and test break-glass accounts (Section 8) before touching any authentication policy | IAM lead | Two break-glass accounts sign in successfully and are excluded from all Conditional Access policies | Sign-in log entries for the test, exclusion configuration screenshot |
| 3 | Issue and enrol hardware authenticators for every privileged account; require two per person (primary plus spare) | IT operations | Every account on the step-1 list has two registered FIDO2 methods | Per-account authentication-method report |
| 4 | Deploy the enforcement policy in report-only mode for privileged roles | IAM lead | Seven consecutive days of report-only data with zero unexplained failures | Report-only policy impact export |
| 5 | Enforce phishing-resistant MFA for privileged roles; set a hard cut-off date for push and SMS on those accounts | CISO approves; IAM lead executes | Policy is in enforced state; no privileged account can complete sign-in with push or SMS | Policy configuration, sign-in log sample showing method used |
| 6 | Remove push, SMS and voice as registered methods on privileged accounts | IAM lead | Method inventory shows only phishing-resistant methods on those accounts | Authentication-method report, before and after |
| 7 | Roll passkeys to the general workforce by department, with self-service enrolment and a staffed cutover window | IT operations | Enrolment rate per department exceeds the agreed threshold | Enrolment report by department |
| 8 | Restrict, then remove, legacy authentication protocols that cannot present a strong factor | IAM lead | Legacy-auth sign-ins are zero for 30 days, then blocked | Legacy-auth sign-in report across the 30 days |
| 9 | Close the residual holes — VPN, network devices, hypervisor consoles, legacy apps behind their own local auth | Cloud platform owner | Each system on the step-1 list either federates to the IdP or has a documented exception with an expiry date | Exception register with named approver and expiry |
Step 9 is where programs actually die. Sophos's finding was not that victims had no MFA — 97% had some. It was that coverage was inconsistent across VPNs, firewalls and legacy apps. An adversary does not attack your average; they attack your minimum.
The cheap version. Hardware keys cost money, and two per privileged user costs twice that. Here is the honest budget arithmetic: you do not need keys for everyone on day one. You need them for the accounts that can change the world — global/tenant admins, domain admins, cloud organization management accounts, the backup console, and the identity provider itself. In most small organizations that is under fifteen people. Two keys each is a three-figure purchase, not a project. Everyone else gets platform passkeys, which are free and already in the operating systems and browsers you own. Do the fifteen this month. Do the rest this year.
Actionable takeaway: Set a calendar date for killing push and SMS on privileged accounts, put a named owner against it, and enrol break-glass accounts before that date arrives. Not "eventually." A date, on a calendar, with an owner.
Standing privilege is a stored credential that is valuable 24 hours a day and used for perhaps twenty minutes a week. Just-in-time (JIT) elevation shrinks the window in which stealing that credential is worth anything. JIT is not a product. It is five requirements, and you can meet them at very different price points:
That warning is the single most under-appreciated fact about JIT, and it is why Section 10 exists.
The cheap version for an organization that cannot buy a PAM platform. You can get most of the value with things you already own:
AddUserToGroup / PutUserPolicy event outside a recorded activation window is one of the highest-signal, lowest-noise detections you will ever write. GuardDuty ships PrivilegeEscalation:IAMUser/AnomalousBehavior for exactly this class, covering AssociateIamInstanceProfile, AddUserToGroup and PutUserPolicy (GuardDuty IAM finding types).Actionable takeaway: Get the standing membership of your three most powerful groups to zero this quarter, and pair every de-elevation step in your runbooks with an explicit session revocation, because group changes alone are not fast enough to contain anything.
Identity threat detection and response is the discipline of treating your identity provider as a monitored system rather than an assumed-good utility. Chapter 9 owns detection engineering as a practice — the Sigma format, the ADS documentation standard, coverage measurement. This section owns what specifically to watch in identity, and where the data actually lives.
| Signal | Why it matters | Primary source |
|---|---|---|
| High-risk user / risky sign-in | Aggregated IdP risk scoring; raising a user to confirmed-compromised is itself a containment trigger | Entra ID Protection |
| Sign-in from anonymized IP / impossible travel | Classic AiTM and infostealer replay indicators | Entra sign-in logs, Workspace login events |
| MFA method added or changed | Attacker persistence after a help-desk reset or a session hijack | Entra audit logs, Workspace admin audit |
| Admin consent granted to an application | The single most password-reset-proof persistence mechanism in SaaS | Purview Audit Consent to application, Workspace OAuth Token log events |
| New inbox rule or forwarding address | BEC staging; survives password reset | New-InboxRule / Set-InboxRule / Remove-InboxRule, plus Set-Mailbox forwarding, checked separately |
| Directory role assignment outside a JIT window | Privilege escalation, human or non-human | Entra audit logs, CloudTrail IAM events |
| Risky service principal / workload identity flagged | Leaked credential, anomalous sign-in or suspicious API traffic from a non-human principal — the population no MFA prompt guards | Entra ID Protection workload identity risk (Get-MgRiskyServicePrincipal) |
| Service principal or service account authenticating from a new ASN, region or client | Standing machine credentials are the most common MFA-bypass path in 2026 | Entra sign-in logs (service principal sign-ins), CloudTrail, GCP Cloud Audit Logs |
| Instance/role credential used outside AWS | Credentials have left the building | GuardDuty UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS |
| Access key flagged as compromised by threat intel | Amazon observed your key in use by an actor | GuardDuty CredentialAccess:IAMUser/CompromisedCredentials |
| Audit logging disabled | Defense impairment; treat as an incident on its own | GuardDuty Stealth:IAMUser/CloudTrailLoggingDisabled |
| New access key or IAM user created | Post-compromise persistence | GuardDuty Persistence:IAMUser/AnomalousBehavior (CreateAccessKey), CloudTrail |
Two ML caveats you must build into expectations. First, GuardDuty documents that if it observes continued activity from a remote host, its model will learn the behavior as expected and stop generating the finding — so persistent exfiltration goes quiet in the console. Do not treat finding volume as a proxy for activity (GuardDuty IAM finding types). Second, Confirm-MgRiskyUserCompromised is not a cosmetic label — it raises the user to high risk, which is a Continuous Access Evaluation critical event and feeds the risk model (Entra ID Protection and Graph PowerShell).
Working the risky-user queue from PowerShell, with the scopes Microsoft documents — and then the queue almost nobody works, the risky workload identities. Entra ID Protection scores service principals as well as people, for leaked credentials, anomalous service-principal sign-ins and suspicious API traffic. Nobody gets an MFA prompt on that population, so risk scoring is most of the detection you have.
# Requires Security Administrator plus delegated IdentityRiskEvent.Read.All and
# IdentityRiskyUser.ReadWrite.All. Returns risk detections and current risky users.
Connect-MgGraph -Scopes "IdentityRiskEvent.Read.All","IdentityRiskyUser.ReadWrite.All"
Get-MgRiskDetection -Filter "RiskType eq 'anonymizedIPAddress'" |
Format-Table UserDisplayName, RiskType, RiskLevel, DetectedDateTime
Get-MgRiskyUser -Filter "RiskLevel eq 'high'" |
Format-Table UserDisplayName, RiskDetail, RiskLevel, RiskLastUpdatedDateTime
# Confirm compromise. This raises the user to high risk and is a CAE critical event.
Confirm-MgRiskyUserCompromised -UserIds "<id1>","<id2>"
# Risky workload identities. Needs the additional delegated scope
# IdentityRiskyServicePrincipal.Read.All (ReadWrite to dismiss or confirm),
# and workload identity risk is a separately licensed Entra capability —
# confirm your tenant carries it before you depend on this queue.
Connect-MgGraph -Scopes "IdentityRiskyServicePrincipal.Read.All"
Get-MgRiskyServicePrincipal -Filter "RiskLevel eq 'high'" |
Format-Table DisplayName, AppId, RiskLevel, RiskDetail, RiskLastUpdatedDateTimeWork that second queue on the same cadence as the risky-user queue. A high-risk service principal has no help desk to call and no human to notice a strange prompt; if you are not reading the list, nobody is.
You cannot detect or investigate in a window you did not retain. These are the numbers, and they are unforgiving.
| Platform | Log | Retention |
|---|---|---|
| Entra ID | Audit logs, sign-ins | 7 days Free; 30 days P1/P2 |
| Entra ID | Risky sign-ins | 7 days Free; 30 days P1; 90 days P2 |
| Entra ID | Risky users | No limit |
| Entra ID | Microsoft Graph activity logs | P1/P2 only, and not retained at all unless routed to storage or analytics |
| Microsoft 365 | Purview Audit (Standard) | 180 days for records generated on or after 2023-10-17; Premium 1 year; 10 years requires the add-on plus a retention policy that is actually created and targeted |
| Google Workspace | Admin, login, Drive, OAuth token, SAML, device, Chat log events | 6 months |
| Google Workspace | Email log search | 30 days |
| AWS | CloudTrail Event history (console) | 90 days of management events |
| AWS | CloudTrail Lake event data store | Up to 3,653 days (~10 yrs) on one-year extendable pricing |
| GCP | Admin Activity and System Event audit logs | 400 days, not configurable, not deletable |
| GCP | Data Access and Policy Denied logs | 30 days by default, and Data Access is off by default except BigQuery |
Sources: Microsoft Entra data retention, Manage audit log retention policies, Google Workspace data retention and lag times, CloudTrail concepts, Cloud Logging retention.
Three sentences that belong on a wall somewhere. Microsoft: "Log retention changes aren't retroactive. When you upgrade from Free to P1 or P2, only data still within the free retention period (up to seven days) is available. Data that has already expired can't be recovered unless it was previously archived." Google: "Administrators cannot delete log event data or change the length of time that the data is available" — good for evidence integrity, unhelpful if you need more than six months. And AWS: "By default, trails and event data stores log management events, but not data or Insights events."
Two lag figures will produce false negatives in a rushed investigation if you do not know them. Purview Audit search: "It can take from 30 minutes up to 24 hours for the corresponding audit log entry to be displayed in the search results after an event occurs" (Detect and remediate illicit consent grants). Google Workspace OAuth Token log events lag by a couple of hours. A consent-grant hunt run five minutes after the grant will come back clean, and clean will be wrong.
Actionable takeaway: Today, check what your identity logs actually retain and route them to storage that outlives your median dwell time. Retention is the only security control that you cannot apply retroactively.
This is the fastest-growing identity population in every environment I have looked at, and most organizations cannot produce a count. Chapter 6 owns cloud workload architecture and Chapter 11 owns third-party SaaS integrations; this section owns the identity-plane question — how many non-human principals exist, who owns them, and how you kill one.
Two 2026 incidents make the case better than any statistic. In May 2026, Sysdig observed an LLM-driven actor that, after an initial exploit, replayed a projected Kubernetes service-account token to dump the cluster Secret store — database credentials, AWS keys, API keys. The agent needed no additional exploit; it used the access its runtime already carried (Sysdig). In March 2026, TeamPCP backdoored a GitHub Action; LiteLLM's CI auto-installed the poisoned tool, which stole LiteLLM's PyPI publishing tokens, and malicious wheels shipped to users days later (Resecurity; LiteLLM security update). Both are pure non-human identity events. No human credential was involved at any point, so no password reset and no MFA policy would have touched either one.
Mandiant's list of how threat actors bypass MFA is, essentially, a list of non-human identities: harvesting long-lived OAuth tokens, stealing session cookies, compromising third-party SaaS vendors, and stealing hard-coded keys and personal access tokens (M-Trends 2026).
| Population | Where it lives | How you revoke it |
|---|---|---|
| Cloud service principals / app registrations | Entra ID, Workspace marketplace apps | Remove-MgOauth2PermissionGrant (delegated) and Remove-MgServicePrincipalAppRoleAssignment (application permissions); Workspace tokens.delete |
| IAM role sessions | AWS STS | Attach the AWSRevokeOlderSessions inline policy and change permissions — see Section 10 |
| Long-lived keys / PATs | IAM users, CI systems, package registries | Deactivate before creating the replacement; rotate the downstream consumer, then delete |
| Workload identity | GCP service accounts, IRSA/EKS, AKS federated credentials | Disabling a key is not enough — see below |
| Kubernetes service-account tokens | Cluster, projected into pods | Delete the bound object or the service account, then strip the RBAC binding |
The GCP trap is the one that catches experienced people. Google documents it directly: "Disabling a service account key does not revoke short-lived credentials that were issued based on the key." The documented remedy is to disable or delete the service account itself, which immediately stops any workload using it (Disable and enable service account keys).
# Disable a specific service account key. This does NOT revoke short-lived
# credentials already minted from it — the service account itself must be
# disabled or deleted for that.
gcloud iam service-accounts keys disable KEY_ID \
--iam-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.com \
--project=PROJECT_IDKubernetes has the cleanest revocation semantics of any platform, because modern tokens are bound to an API object. If the referenced object is deleted or does not exist, or its metadata.uid does not match, "authentication with that token fails immediately"; for objects pending deletion with finalizers, tokens fail 60 seconds after deletionTimestamp (Managing Service Accounts).
# Kill every token for a service account. Deleting the SA does NOT remove the
# RoleBindings/ClusterRoleBindings — strip those too, or a recreated SA of the
# same name inherits the grant.
kubectl delete serviceaccount <sa> -n <ns>
# Mint a deliberately scoped, bound token instead of a long-lived one.
kubectl create token my-sa --bound-object-kind="Pod" --bound-object-name="test-pod"The Salesloft Drift compromise remains the best teaching case for standing tokens: attackers stole the OAuth refresh tokens customers had issued to Drift and over ten days exported records from 700+ organizations. The highest-value loss was secondary — API keys, Snowflake tokens, cloud credentials and passwords that customers had pasted into support-case text (AppOmni; Cloud Security Alliance).
Treat support tickets, chat transcripts and wiki pages as a credential store, because that is empirically what they are. Scan them. Rotate what you find. Then fix the process that put it there.
The cheap version. A managed secrets vault is the right answer and it costs money. If you cannot buy one yet: (1) turn on secret scanning in your source control — most platforms include it at no cost; (2) replace static CI credentials with short-lived OIDC federation, which is a configuration change rather than a purchase; (3) pin third-party CI actions by commit SHA rather than by tag, which is free and would have blunted the Trivy-to-LiteLLM chain; (4) maintain one spreadsheet of every long-lived key with owner, system, creation date and last-rotated date, and rotate anything over a year old. Not glamorous. Effective.
Actionable takeaway: Produce a non-human identity inventory with a named human owner for every entry, and add a "who owns this and how do I revoke it in one command" column. An unowned service principal is a backdoor that passed a change-approval board.
The map has a node for it because 2026 demands one. An autonomous agent that acts inside your environment is a principal. If you have not decided what kind of principal it is, you have decided by default — and the default is almost always "it borrows a human's token," which is a confused-deputy problem with a launch date.
Here is the failure mode in one sentence. The agent has more context than the human who invoked it, acts faster than the human can supervise, and carries the human's full authority — so when untrusted content reaches it, the content is executing with your privileges under your name in your audit log. OWASP's Top 10 for Agentic Applications 2026 names the categories directly: ASI01 Agent Goal Hijack, ASI02 Tool Misuse, ASI03 Identity and Privilege Abuse (OWASP GenAI). The structural cause, which Chapter 7 develops fully, is that LLMs process instructions and data on the same channel — there is no reliable in-band separation between content and command, so every model-adjacent data source is untrusted input to a privileged executor.
The Sysdig case is the confused-deputy pattern already in production: the agent inherited a service-account token from a mounted projected volume and replayed it. And the Nx "s1ngularity" campaign of August 2025 is the inverse — malicious package versions detected developer AI CLIs on the machine and invoked them with permission-bypassing flags to enumerate secrets across the filesystem, harvesting 2,349 credentials from 1,079 developer systems (The Hacker News; GitGuardian). An agent that will do anything you ask is an agent that will do anything anyone asks.
Four requirements, and they map to the same primitives as every other identity in this chapter:
| Requirement | What it means concretely |
|---|---|
| Authenticate as itself | The agent holds its own workload identity — a service principal, a federated workload credential, a bound service-account token. It never authenticates with a human's refresh token, session cookie or personal access token. |
| Authorize with its own scope | Permissions are granted to the agent identity for the specific tools and data it needs, not inherited from the invoking user. Where the agent must act for a user, it holds a delegated grant with the intersection of agent scope and user scope, and the delegation is recorded. |
| Be attributable | Every action carries the agent identity plus the invoking human plus the session. "Closed by agent" with no evidence is how a real incident gets buried — the same rule Chapter 17 applies to SOC automation. |
| Be revocable in one action | There is a single documented command that stops this agent everywhere. If revocation requires visiting four consoles, you do not have a revocation procedure; you have a wish. |
Add two operational rules. First, short-lived credentials only — an agent that runs for four minutes should not hold a credential that lives for twelve hours. Second, the lethal trifecta: private data access plus untrusted content plus external communication in one agent is the combination that turns prompt injection into exfiltration (Simon Willison; Microsoft, "The state of MCP security in 2026"). Break one leg of it — usually the external communication, by allowlisting egress — and the class of attack collapses.
The cheap version. You do not need an agent-identity platform. You need a register: one row per deployed agent, with its identity, its scopes, its owner, its revocation command, and the date someone last looked at it. If an agent is not in the register, it does not get production credentials.
Actionable takeaway: Ban human-token impersonation for agents in policy this quarter, and give every deployed agent its own scoped, short-lived, revocable identity — then test the revocation command and record how long it took.
Every control in this chapter assumes your identity provider is working and trustworthy. Break-glass is the procedure for the day it is neither.
The requirements are not negotiable and they are not expensive:
| # | Requirement | Why it fails without this |
|---|---|---|
| 1 | At least two accounts, cloud-only, not synchronized from on-premises directory | A single account is a single point of failure; a synced account dies with the directory |
| 2 | Excluded from every Conditional Access policy, including vendor-managed ones | A CA policy misconfiguration is one of the most common ways organizations lock themselves out of their own tenant |
| 3 | Phishing-resistant MFA that does not depend on the production identity provider or on a personal device | An emergency account gated behind the system that is down is decoration |
| 4 | Credentials split and physically secured — sealed envelopes in separate safes, or an offline password manager under dual control | If one person can use it alone and silently, it is not break-glass, it is a backdoor |
| 5 | Alerting on any sign-in or authentication attempt, routed to the SOC and to a named executive | Break-glass use should page a human within minutes, every time |
| 6 | Excluded from automated lifecycle processes — no expiry, no disablement by an inactivity job | The most common failure is discovering during an outage that a cleanup script disabled the account |
| 7 | Tested at least twice a year — the IG1 floor; Chapter 18 sets quarterly as the IG2 target — with the test logged and the alert verified to have fired | An untested emergency credential has roughly a coin-flip chance of working |
Microsoft's guidance is explicit on requirement 2: exclude break-glass and emergency-access accounts from every Conditional Access policy, including Microsoft-managed ones, and use report-only mode before enforcing any new policy (Conditional Access — Block access; Microsoft-managed CA policies).
Extend the same logic to the systems you will need during an identity compromise. The clearest documented statement of the principle comes from backup architecture: the repository is a separate trust domain whose credentials never live in the backup control plane, so compromising the backup server does not compromise the backups (Veeam Hardened Repository). Generalized: backup and recovery infrastructure must not authenticate against the identity provider you are trying to recover. If the backup console uses tenant SSO and the tenant is compromised, you cannot log in to restore. Chapter 12 develops this; the identity-side rule is dedicated non-SSO emergency credentials for backup and recovery systems, stored offline, with MFA that does not depend on the production identity provider.
Actionable takeaway: Schedule the break-glass test as a recurring calendar item with a named owner, and treat a failed or unalerted test as a SEV-3 incident with an after-action item, not as a chore to reschedule.
CISA's advisory describes the technique precisely: research employees on business platforms and social media, then call the IT help desk posing as them to obtain password resets and MFA token transfers to attacker-controlled devices, often splitting the request across separate contacts to evade detection (CISA AA23-320A). Vishing is now the number two initial infection vector globally, at 11% of Mandiant investigations (M-Trends 2026).
And the caller now sounds exactly right. In the Arup case, an employee's justified scepticism about a phishing email was overcome by a multi-person video conference in which every other participant was AI-generated, resulting in approximately US$25.6 million lost across 15 wire transfers in a single day (CNN).
Look at what stopped the attacks that were stopped. Ferrari: an executive challenged a CEO voice clone with a shared-secret question — a recently recommended book — that the clone could not answer (AI Incident Database). LastPass: an employee flagged the channel anomaly, calls and WhatsApp voicemail from a supposed CEO, rather than detecting the fake (Adaptive Security). WPP: employee vigilance against a Teams meeting using a voice clone and public footage (OECD AI Incidents). All three were stopped by a human process check, not by detection technology. Encode the process check.
This is written to be read aloud by an agent under time pressure. It contains no jokes for the same reason a fire door contains no window.
Applies to: any inbound request for a password reset, MFA method addition or reset, MFA device transfer, account unlock, or contact-detail change on an account.
| # | Step | Agent action | Fails if |
|---|---|---|---|
| 1 | Classify the account | Look up the requester. If the account holds any privileged role, stop and route to the privileged path (step 7). | — |
| 2 | Terminate the inbound channel | "I'm going to verify you and call you back on the number in our directory." End the call. Do not accept a number supplied by the caller. | Caller objects to the callback, cites urgency, or supplies an alternative number |
| 3 | Call back out-of-band | Dial the number of record in the HR directory, not the caller ID, not the ticket. | No answer on the number of record, and the caller then calls in again |
| 4 | Verify identity on a second factor | Require one of: a live video call with a government photo ID visible; verification by the requester's manager contacted independently; or a pre-enrolled challenge phrase. Never knowledge-based questions built from public data. | Requester cannot complete any of the three |
| 5 | Check for the split request | Search the ticket queue for any other request touching this account in the last 72 hours, including from other agents and other channels. | A related request exists that this agent did not raise |
| 6 | Perform the action and log it | Complete the reset. Record: verification method used, who performed it, callback number dialed, timestamp. | — |
| 7 | Privileged path | For privileged accounts: manager or department head must approve in a separate channel, and a security team member must approve. Two approvals, two channels, both logged. | Either approval is missing |
| 8 | Notify | Send an automated notification to the account holder's registered address and to the SOC that a credential or MFA change occurred. | — |
Two supporting controls make the script survivable. First, an agent must never be penalized for a refusal that turns out to be a legitimate user. If your service-desk metrics punish handle time, the script loses to the metric every single time. Fix the metric. Second, you need a tenant-wide MFA re-enrolment freeze as a named, pre-authorized capability — a switch the Incident Commander can throw that stops all help-desk-initiated MFA enrolment while an identity incident is live. Write it, test it, and know who can authorize it before you need it at 3am.
Actionable takeaway: Put the verification script on the wall behind the service desk this week, remove handle-time penalties for refusals, and run one unannounced test call per month against your own agents.
This is the persistence mechanism that survives everything you would normally do. Microsoft, again, in the plainest possible terms: "Normal remediation steps (for example, resetting passwords or requiring multifactor authentication (MFA)) aren't effective against this type of attack, because these apps are external to the organization" (Detect and remediate illicit consent grants).
The FBI's IC3 issued PSA260901 in September 2026 describing an active campaign running since late 2025: actors register malicious applications with legitimate OAuth providers, named to resemble file-storage or identity-verification services, then contact targets impersonating journalists, academics or event organisers and induce them to approve permissions through a genuine Microsoft or Google consent screen. The result is persistent read and send mail access plus file access, without the password — and changing the password does not revoke it (Help Net Security reporting IC3 PSA260901).
The enterprise-scale version was the 2025 Salesforce vishing campaign, in which attackers posing as internal IT induced employees to authorize a malicious Connected App granting OAuth access — with no platform vulnerability involved at any point, and roughly 91 claimed victim organizations (Krebs on Security; ReliaQuest).
Turn off end-user consent for applications, or restrict it to a vetted, low-risk permission set with an admin-consent request workflow. Microsoft explicitly recommends against the blunt instrument of turning off integrated applications tenant-wide — so do the surgical version: restrict, review, approve.
# Tenant-wide inventory of delegated and application permissions, using
# Microsoft's documented method. Triage the CSV on ConsentType = AllPrincipals
# (the app can reach everyone's content in the tenant), on Permission values
# containing Write or .All, and on unfamiliar ClientDisplayName values.
.\Get-AzureADPSPermissions.ps1 | Export-csv -Path "Permissions.csv" -NoTypeInformationOne prerequisite, because that leading .\ quietly assumes a file that is not there. Get-AzureADPSPermissions.ps1 is a community script that Microsoft links to from its illicit-consent-grant page — it is not a cmdlet, not part of any module, and not present on any machine by default. Download it, read it, and stage it in your responder toolkit now, while nothing is on fire. Pulling an unreviewed script off the internet and running it against your tenant mid-incident is not a plan; it is a second incident.
In Purview Audit, search the activity Consent to application and inspect each record for IsAdminConsent: True, which indicates someone with Global Administrator access granted broad tenant-wide access. Remember the 30-minute-to-24-hour indexing lag before you declare the tenant clean.
At scale, Defender XDR advanced hunting exposes the CloudAppEvents table with an OAuthAppId column plus ActionType, AccountObjectId, IPAddress, UserAgent, IsAdminOperation, and the two anomaly-scoring columns LastSeenForUser and UncommonForUser. Critical caveat: the table is populated only if Defender for Cloud Apps is deployed and the Microsoft 365 activities connector is enabled — queries silently return nothing otherwise (CloudAppEvents table). An empty result is not evidence of absence; verify the connector first.
In Google Workspace, OAuth Token audit logs record "each time a third-party application is authorized to access Google Account data," queryable via Activities.list() with applicationName=token (OAuth log events). Retention is six months; lag is a couple of hours.
# Revoke a delegated consent grant.
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId <id>
# Revoke an application-permission role assignment on a service principal.
Remove-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId <sp-id> -AppRoleAssignmentId <assignment-id># Google Workspace: revoke one application's token for one user.
# Scope: https://www.googleapis.com/auth/admin.directory.user.security
DELETE https://admin.googleapis.com/admin/directory/v1/users/{userKey}/tokens/{clientId}Scoping the blast radius afterwards requires mailbox auditing and admin/user activity auditing to have been enabled before the attack. Microsoft flags this explicitly, and it is the same retroactivity problem as Section 4: you cannot buy the past.
Actionable takeaway: Restrict end-user OAuth consent this quarter, run the tenant-wide permission inventory monthly, and add "enumerate and revoke OAuth grants" as an explicit branch of every identity containment runbook you own.
This is the most important operational detail in the chapter. Chapter 14.3 (SaaS and Cloud Account Takeover) and Chapter 14.4 (Identity Provider and Privileged Credential Compromise) are the full incident playbooks. This section is the control-design version: the order, the reason, and the verified commands.
A refresh token is an independent bearer credential. It does not care about your password. In Entra ID, a password change is a Continuous Access Evaluation critical event — but CAE reaches only CAE-capable resource providers (Exchange Online, SharePoint Online, Teams, Graph), only after up to 15 minutes of propagation, never for guest accounts, and never for an application's own session cookie or a consented OAuth grant (Continuous access evaluation).
So a reset-only response leaves you with:
That is the entire argument. Revocation is the control that matters; expiry is not.
The same asymmetry exists in AWS, expressed differently: "Temporary security credentials are valid until they expire… You can revoke these credentials, but you must also change permissions for the IAM user or role" (Disabling permissions for temporary security credentials). Session duration runs 900 seconds to 36 hours, defaulting to 12. Revoking sessions without changing permissions means the attacker re-assumes the role thirty-one seconds later.
And in Google Workspace: signOut resets sign-in cookies but does not revoke a third-party OAuth grant — the app keeps working. Both calls are needed.
Disabling is loud, immediate, and irreversible in its effect on your telemetry. It is the moment the adversary learns they are detected — and the moment you stop generating the sign-in logs, mail-access records and API events you were about to use to find their other footholds. In Entra specifically, re-enabling a disabled user has a documented 15-minute lag for SharePoint and Teams and 35 to 40 minutes for Exchange Online, so a premature disable you have to undo costs you most of an hour (Continuous access evaluation).
The sequence below is a synthesis built on those vendor facts, not a vendor statement. Microsoft's own documented per-user emergency order is: disable account → revoke sign-in session → disable registered devices, with the on-premises AD steps first in a hybrid environment. Use Microsoft's order when you already know the scope and want the account gone. Use the sequence below when you are still learning what the adversary touched.
| # | Action | Who | Done when | Evidence to capture |
|---|---|---|---|---|
| 1 | Preserve. Place the legal/eDiscovery hold and start log export before any containment action | Legal Liaison approves; Operations Lead executes | Hold is applied to the mailbox, drive and site; export job is running | Hold confirmation, export job ID, timestamps |
| 2 | Scope, time-boxed. Enumerate sessions, OAuth grants, inbox rules and forwarding, registered devices, MFA methods, role assumptions and created credentials | Operations Lead | The enumeration is complete or the time box expires, whichever comes first | Output of each enumeration command, saved with hashes |
| 3 | Hybrid only: on-premises AD first — disable the account and reset the password twice | Operations Lead | Both resets complete and have replicated | Command output, replication confirmation |
| 4 | Revoke sessions and reset the credential in one atomic burst — never the reset alone, never the reset first | Operations Lead | Session revocation and credential reset are both confirmed | signInSessionsValidFromDateTime value, reset confirmation |
| 5 | Revoke OAuth grants and app-role assignments for the principal | Operations Lead | No non-Microsoft grants remain for the account | Before/after permission export |
| 6 | Remove attacker-created persistence — inbox rules, mailbox forwarding, added MFA methods, new app registrations, new access keys, new IAM users | Operations Lead | Each persistence class is checked and cleared | Per-class query output, before and after |
| 7 | Disable or quarantine registered devices | Operations Lead | Devices show disabled | Device list export |
| 8 | Change permissions, not just sessions (cloud) — attach a deny policy or quarantine SCP so the principal cannot simply re-assume | Cloud platform owner; SCP requires Incident Commander approval | The principal's API calls fail | Policy ARN/ID, CloudTrail showing denied calls |
| 9 | Then decide on disable. Disable the account if it is not needed; otherwise apply a block policy so the identity keeps generating telemetry while being useless | Incident Commander | Decision is recorded with rationale | Decision log entry |
| 10 | Verify by observation, not assumption — no new tokens issued, no new sign-ins, no new API calls from the principal | Operations Lead | 60 minutes of clean telemetry across all platforms in scope | Query output covering the verification window |
Microsoft Entra ID / Microsoft 365 — hybrid on-premises steps first. Microsoft's stated reason for the double reset is "to mitigate the risk of pass-the-hash, especially if there are delays in on-premises password replication" (Revoke user access in an emergency).
# On-premises Active Directory (hybrid environments only).
# Disable, then reset the password TWICE to clear the hash history.
Disable-ADAccount -Identity johndoe
Set-ADAccountPassword -Identity johndoe -Reset `
-NewPassword (ConvertTo-SecureString -AsPlainText "<random1>" -Force)
Set-ADAccountPassword -Identity johndoe -Reset `
-NewPassword (ConvertTo-SecureString -AsPlainText "<random2>" -Force)# Entra ID. Requires User Administrator for standard accounts and
# Privileged Authentication Administrator for admin accounts.
# Revoke-MgUserSignInSession invalidates refresh tokens and browser session
# cookies by resetting signInSessionsValidFromDateTime.
Connect-MgGraph -Scopes "User.ReadWrite.All","Directory.AccessAsUser.All"
$User = Get-MgUser -Search UserPrincipalName:'johndoe@contoso.com' -ConsistencyLevel eventual
Revoke-MgUserSignInSession -UserId $User.Id
Update-MgUser -UserId $User.Id -AccountEnabled:$false
# Disable the user's registered devices. Requires Cloud Device Administrator.
Get-MgUserRegisteredDevice -UserId $User.Id -All | ForEach-Object {
Update-MgDevice -DeviceId $_.Id -AccountEnabled:$false
}These next cmdlets are not Microsoft Graph. Get-InboxRule, Get-Mailbox, Set-Mailbox and Search-UnifiedAuditLog are Exchange Online PowerShell cmdlets: they come from the ExchangeOnlineManagement module and need their own Connect-ExchangeOnline session, separate from the Connect-MgGraph session above, and audit search additionally requires an Exchange Online audit-log role assignment. Discovering that at 03:00, via "the term Get-InboxRule is not recognized," is a bad use of an hour. Install the module and prove the connection works in peacetime.
# Exchange Online PowerShell — a separate module and a separate session from
# Connect-MgGraph. Audit search also requires an Exchange Online audit-log role.
# Install-Module ExchangeOnlineManagement # once, in peacetime
Connect-ExchangeOnline -UserPrincipalName <admin-upn>
# Check for attacker-created mail persistence. Microsoft's named operations are
# exactly three. Mailbox-level forwarding does NOT appear in Get-InboxRule
# output and must be checked separately with Get-Mailbox.
Get-InboxRule -Mailbox <mailbox> | FL Name,Description,DeleteMessage,MoveToFolder,Enabled
# Re-run this call with the SAME SessionId until it returns zero rows.
Search-UnifiedAuditLog -StartDate <start> -EndDate <end> -UserIds <user1,user2> `
-Operations New-InboxRule,Set-InboxRule,Remove-InboxRule `
-SessionCommand ReturnLargeSet -SessionId <id> -ResultSize 1000Do not drop -SessionCommand. Without it the cmdlet returns a maximum of 100 records no matter what you put in -ResultSize, and a truncated result in an investigation reads exactly like a clean one. ReturnLargeSet returns unsorted data in pages and must be re-run with the same SessionId until it yields zero rows; -ResultSize is capped at 5,000 records per call.
AWS. The console's "Revoke active sessions" action attaches an inline policy named AWSRevokeOlderSessions to the role; the required permission is PutRolePolicy. It denies all access to sessions assumed in the past and approximately 30 seconds into the future, to absorb policy-propagation delay (Revoke IAM role temporary security credentials).
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"DateLessThan": {"aws:TokenIssueTime": "2014-05-07T23:47:00Z"}
}
}
}# Attach the revocation policy programmatically with the timestamp you choose.
aws iam put-role-policy --role-name <role> \
--policy-name AWSRevokeOlderSessions \
--policy-document file://revoke.json
# Deactivate a compromised long-term access key. For a COMPROMISED key this
# order is inverted from the normal rotation sequence: deactivate first, then
# create the replacement. Do not delete until you have confirmed nothing broke.
aws iam update-access-key --user-name <user> --access-key-id <AKIA...> --status Inactive
# Attach an SCP from the management account so a member-account admin cannot
# detach it. Target may be a root (r-*), an OU (ou-*), or a 12-digit account ID.
aws organizations attach-policy --policy-id p-examplepolicyid111 \
--target-id ou-examplerootid111-exampleouid111Three constraints that break naive AWS playbooks. You cannot revoke the session for a service-linked role. Roles created from IAM Identity Center permission sets cannot be edited in IAM — revoke the active permission set session in Identity Center instead. And if a resource-based policy independently allows the principal, revoking the role session is not sufficient; add an explicit Deny on the resource keyed on aws:PrincipalArn or aws:SourceIdentity. Clients cache credentials, so force a refresh with rm -r ~/.aws/cli/cache.
Google Workspace. Both calls are required — the first kills sessions, the second kills the app grant.
# Sign the user out of all web and device sessions and reset sign-in cookies.
# Scope: https://www.googleapis.com/auth/admin.directory.user.security
POST https://admin.googleapis.com/admin/directory/v1/users/{userKey}/signOut
# Revoke a specific third-party application's OAuth token. signOut alone does
# NOT do this — the app keeps working. Enumerate first with tokens.list.
DELETE https://admin.googleapis.com/admin/directory/v1/users/{userKey}/tokens/{clientId}If you believe krbtgt or a Tier-0 asset is compromised, per-user actions are noise until the domain is dealt with. The krbtgt account is reset twice, because the account has a two-password history, with at least 10 hours between the resets so the first fully replicates — longer if you have modified ticket lifetimes (CISA Eviction Strategies Tool CM0050). Chapter 12 covers identity-first recovery ordering and AD forest recovery in full.
Actionable takeaway: Rewrite every identity runbook you own so that token and session revocation appears before or alongside the credential reset, never after it — and add a verification step that confirms no new tokens were issued. Do it this week. Not next sprint. This week.
An access review that produces a screenshot of someone clicking "approve all" is not a control, it is a rehearsal for an audit finding. A useful review produces three artefacts: a list of what was reviewed, a record of what changed as a result, and a named person who owns the decision.
| Scope | Cadence | Reviewer | Evidence required |
|---|---|---|---|
| Privileged roles (all platforms) | Monthly | System owner, countersigned by CISO or delegate | Role membership export before and after, list of removals, dated attestation |
Standing service principals and app registrations with write or .All permissions | Monthly | Cloud platform owner | Permission inventory export, removal list |
OAuth grants with ConsentType = AllPrincipals | Monthly | IAM lead | Grant inventory, business justification per retained grant |
| General workforce access to sensitive data systems | Quarterly | Data owner | Entitlement export, manager attestation per user |
| Non-human identities and API keys | Quarterly | Named owner per identity | Owner confirmation, last-used date, rotation date |
| Guest and external accounts | Quarterly | Sponsoring manager | Guest list with sponsor and expiry per account |
| Joiner / mover / leaver reconciliation against HR records | Monthly | IAM lead | Exception list — accounts in the directory with no matching HR record, and the reverse |
The mover case is where entitlements quietly accumulate. Someone moves from finance to engineering and keeps both sets of access, and three moves later they can approve a payment, deploy to production and read the HR drive. The reconciliation that catches this is a comparison of role assignments against the HR record of the current job, not against last year's review.
The cheap version. AWS IAM Access Analyzer has unused access analyzers — unused roles, unused access keys, unused passwords, unused services and actions on active principals — and they are not Region-dependent (IAM Access Analyzer). That is your least-privilege lever without buying anything. Access Analyzer's policy generation from CloudTrail activity is also how you rebuild a scoped role after ripping permissions off a compromised one. Pair it with a quarterly export-to-spreadsheet review and you have a defensible program.
Two mechanical details that make reviews stick. Set the default answer to remove, so silence revokes rather than retains — the reviewer has to act to keep access, not to remove it. And review entitlements, not group names: "member of SG-Fin-App-RW" tells a manager nothing, while "can approve payments up to $50,000" tells them everything.
Actionable takeaway: Move privileged access reviews to monthly, set the default outcome to removal, and require that each review produce a dated before-and-after export — because next year the only thing that will exist is the artefact.
Chapter 5 turns the identity decision into a network-level enforcement point. Chapter 6 covers cloud workload identity, IMDS and CIEM. Chapter 7 covers AI governance and the agentic risk categories this chapter only touches. Chapter 9 covers detection engineering and coverage measurement. Chapter 11 covers vendor OAuth integrations as third-party risk. Chapter 12 covers identity-first recovery ordering. Chapters 14.3 and 14.4 are the executable playbooks for account takeover and identity provider compromise. Chapter 21 sequences all of it into a 180-day plan.
If you take one thing from this chapter: identity is the only control domain where the order of your response determines whether it works at all. Everywhere else, doing the right things in the wrong order is inefficient. Here, it is the difference between evicting an adversary and announcing yourself to one who is still holding a valid token.
Stay enrolled, stay revoked, and never reset a password before you have killed the session.
[IG1] [PR.AA] [CIS 5][IG1] [PR.AA] [CIS 6][IG2] [PR.AA] [CIS 6][IG3] [PR.AA][IG1] [PR.AA] [CIS 6][IG2] [PR.AA] [CIS 5][IG1] [PR.AA] [CIS 5][IG2] [RS.MI][IG1] [DE.CM] [CIS 8] [A.8.15][IG2] [DE.CM] [A.8.16][IG2] [PR.AA] [CIS 5][IG2] [PR.AA][IG1] [PR.AA] [CIS 3][IG2] [PR.AA][IG2] [ID.AM] [PR.AA][IG1] [PR.AA]EX-15 in Chapter 18 — with the test and the alert both logged. [IG1] [DE.CM] [PR.AA][IG2] [PR.AA] [RC.RP][IG1] [PR.AA] [PR.AT][IG2] [PR.AT][IG3] [RS.MI]AllPrincipals grants; any community script or module the inventory depends on is downloaded, reviewed and staged in the responder toolkit in peacetime, along with the ExchangeOnlineManagement module and a tested Connect-ExchangeOnline path. [IG2] [PR.AA] [CIS 6][IG1] [RS.MI][IG1] [PR.AA] [CIS 5] [CIS 6][IG2] [PR.AA] [CIS 5][IG2] [DE.CM] [PR.AA]