In this section
3.4 Identity Evidence Auto-Collection
Scenario
At 14:30, the AiTM alert fires for d.chen@northgateeng.com. The attacker registered a new MFA method at 14:30, consented to a malicious OAuth app at 14:35, and created two inbox rules at 14:32. Containment fires at 14:33, it removes the attacker's MFA method, revokes the OAuth consent, and deletes the inbox rules. At 15:15, Tom opens the investigation. The attacker's MFA method is gone. The OAuth consent is gone. The inbox rules are gone. If nobody captured the directory state before containment, Tom has to reconstruct what the attacker changed from audit logs alone. The identity snapshot captures the live state at detection time: every MFA method, every consent, every rule, with attacker artifacts flagged.
Why identity evidence is different from log evidence
Identity evidence captures the compromised user's current configuration state, what the settings look like right now. This is distinct from cloud log evidence (Section 3.2), which records what changed. The AuditLogs entry says "User registered security info." The identity snapshot says "Here are the three MFA methods currently registered, and this one was registered 30 seconds ago from the attacker's IP."
Logs tell you that something changed. The identity snapshot tells you what the configuration looks like as a result of those changes. Containment modifies the live configuration (removes MFA methods, revokes consents, deletes rules) but does not modify the logs. After containment, the logs still show that the attacker registered an MFA method, but the live directory no longer contains that method. The identity snapshot captured before containment is the only record of the attacker's artifacts in their complete state.
The operational consequence is significant. Without the pre-containment snapshot, the investigator at 15:15 sees a clean directory and has to work backwards from AuditLogs to reconstruct what existed. AuditLogs tell you a method was registered. They don't tell you the device name, the app version, or whether the method was the primary or secondary authenticator. AuditLogs tell you a consent was granted. They don't tell you the full scope list at the time of revocation, only the scopes at the time of consent, which can differ if the app requested incremental permissions. The live snapshot captures the complete object, not a log summary of the event that created it.
The six Graph API calls
The identity collection makes six Graph API calls, each capturing a specific configuration domain. All six use managed identity authentication against https://graph.microsoft.com.
Current MFA methods — GET /users/{userId}/authentication/methods. Returns all registered authentication methods with their IDs, types, device names, and creation dates. This snapshot is the highest-priority identity evidence because the containment playbook (SA5.3) removes attacker MFA methods within 30 seconds. The collection captures the pre-containment state showing the attacker's registered method alongside the user's legitimate methods.
The response returns an array of method objects, each with an @odata.type that identifies the method kind: #microsoft.graph.microsoftAuthenticatorAuthenticationMethod for the Authenticator app, #microsoft.graph.phoneAuthenticationMethod for SMS/voice, #microsoft.graph.fido2AuthenticationMethod for hardware keys. The playbook records the id, @odata.type, displayName, and createdDateTime for every method. The createdDateTime is the field the flagging logic uses: any method created after the attacker's first sign-in gets flagged. A subtlety: the phone method type does not expose createdDateTime in all tenants. When the timestamp is missing, the playbook flags the method as "unverifiable" and includes it in the attacker section for manual review rather than silently marking it legitimate.
Current OAuth consent grants — GET /users/{userId}/oauth2PermissionGrants. Returns all delegated permission grants with client IDs, scopes, and consent timestamps. The containment playbook (SA5.5) revokes suspicious consents: the collection preserves the pre-containment state for investigation. The key fields are clientId (which app received the consent), scope (which permissions were granted — Mail.ReadWrite is very different from User.Read), and startTime (when the consent was created). The playbook resolves each clientId to an application display name via GET /servicePrincipals/{clientId} and checks verifiedPublisher: an unverified publisher with Mail.ReadWrite consented during the compromise window is almost certainly attacker infrastructure.
Current inbox rules — GET /users/{userId}/mailFolders/inbox/messageRules. Returns all inbox rules with their actions and conditions. The containment playbook (SA5.7) deletes malicious rules: the collection preserves them as evidence.
Current mailbox delegates — GET /users/{userId}/mailboxSettings and GET /users/{userId}/mailFolders/inbox/permissions. Captures delegate access that the attacker may have configured for persistent mailbox access.
Current group memberships — GET /users/{userId}/memberOf. Returns the user's group and role memberships. The attacker may have added the compromised user to privileged groups during the attack.
Conditional access evaluation: the most recent CA evaluation from the SigninLogs ConditionalAccessPolicies field. Shows which CA policies applied to the compromised user's sign-ins and why the attacker was able to authenticate: the AiTM session token satisfies MFA claims, and the CA evaluation shows exactly how the attacker bypassed conditional access.
Method: GET
URI: https://graph.microsoft.com/v1.0/users/{userId}/authentication/methods
Authentication: Managed Identity
Audience: https://graph.microsoft.com
Permissions required on the managed identity: UserAuthenticationMethod.Read.All (MFA methods), Application.Read.All and DelegatedPermissionGrant.ReadWrite.All (OAuth consents), MailboxSettings.Read (inbox rules and delegates), GroupMember.Read.All (group memberships), Policy.Read.All (conditional access evaluation). These are read-only permissions except DelegatedPermissionGrant, which needs ReadWrite because the same managed identity handles revocation in SA5.5.
Flagging attacker artifacts in the snapshot
The raw Graph API responses list all MFA methods, all consents, all rules, including the user's legitimate items. The collection playbook adds flags by cross-referencing each item against the incident timeline.
For MFA methods: compare each method's registration date against the incident's earliest sign-in from the attacker IP. Methods registered after the attacker's first sign-in are flagged as attacker artifacts. Methods registered before are marked legitimate.
For OAuth consents: compare each consent's startTime against the compromise window. Check the publisher verification status. Unverified publishers consented during the compromise window are flagged. Verified publishers consented months ago are marked safe.
For inbox rules: cross-reference rule creation timestamps from AuditLogs with the compromise window. Rules created from the attacker IP are flagged. Rules predating the compromise are marked safe.
This flagging transforms the raw snapshot from "here are all the user's settings" into "here are the settings, and these specific ones are the attacker's." The investigator immediately sees which artifacts to investigate deeper.
Pre-containment vs post-containment comparison
The identity snapshot is captured at two points: once before containment (by SA3, at alert time) and once after containment (by a post-containment step added to the SA5 playbook). The comparison between the two snapshots is the containment record.
Pre-containment: 3 MFA methods (2 legitimate + 1 attacker), 4 OAuth consents (3 legitimate + 1 attacker), 3 inbox rules (1 legitimate + 2 attacker). Post-containment: 2 MFA methods (2 legitimate), 3 OAuth consents (3 legitimate), 1 inbox rule (1 legitimate). The delta: 1 MFA method removed, 1 consent revoked, 2 rules deleted. This comparison provides evidence that containment executed correctly, a record of what was removed for the IR report, and assurance that legitimate artifacts were preserved.
The delta also catches containment failures. If the pre-containment snapshot shows 3 attacker artifacts and the post-containment snapshot still shows 2, containment missed one. This happens more often than expected: the consent revocation API call returns 204 (success) but the consent persists because the service principal has an application-level grant that overrides the delegated revocation. Or the inbox rule deletion targets the rule ID from the initial snapshot, but the attacker created a second rule between snapshot and containment. The comparison is your verification that what should have been removed actually was.
The identity snapshot is versioned: the playbook writes the snapshot with a timestamp and version number: "Identity snapshot v1, pre-containment." If the IR team requests a post-containment snapshot to verify containment was effective, the playbook generates "Identity snapshot v2, post-containment." Comparing v1 and v2 shows exactly what containment changed. The version numbers matter for chain of custody: each snapshot is a distinct evidence artifact with its own SHA-256 hash and timestamp.
Inbox rule collection: the Graph endpoint detail
The inbox rules query deserves specific attention because the Graph API endpoint differs from what most documentation suggests.
GET https://graph.microsoft.com/v1.0/users/{userId}/mailFolders/inbox/messageRules
This returns rules applied to the Inbox folder specifically. Rules created via Outlook desktop or OWA that apply to "all messages" are inbox rules. However, rules created via Exchange Online PowerShell (New-InboxRule) or transport rules (New-TransportRule) may not appear in this endpoint. The playbook supplements the Graph call with a KQL query against AuditLogs for all rule-related operations:
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName has_any ("Set-InboxRule", "New-InboxRule",
"New-TransportRule", "Set-TransportRule", "Add-MailboxPermission")
| where TargetResources[0].userPrincipalName =~ "{UPN}"
or InitiatedBy.user.userPrincipalName =~ "{UPN}"
| project TimeGenerated, OperationName,
InitiatedByIP = tostring(InitiatedBy.user.ipAddress),
Details = tostring(TargetResources[0].modifiedProperties)
This catches rules that the Graph API might miss, particularly transport rules (which are tenant-wide, not per-user) and delegate permissions added via PowerShell. The combination of Graph API (current state) plus AuditLogs (historical changes) provides the complete rule evidence.
Anti-Pattern
Capturing identity state after containment has already modified it
If the identity snapshot runs after containment, the attacker's MFA method is already removed, the OAuth consent is already revoked, and the inbox rules are already deleted. The snapshot shows the post-containment state, which looks like a normal user's directory: no evidence of the attacker's modifications. The investigation team then has to reconstruct the attacker's changes entirely from audit logs, which is possible but slower and less reliable than a direct snapshot. The identity collection must run as wave 1 (first 10 seconds after incident creation) because containment fires at wave 1 + 30 seconds.
Error handling for Graph API collection
Graph API calls occasionally fail during collection. The playbook handles each failure type differently because the cost of missing evidence varies by source.
MFA method enumeration failure (403): the managed identity is missing UserAuthenticationMethod.Read.All. This is a deployment error: the permission was not granted during playbook setup. The playbook logs the specific permission gap. This error is persistent and will not resolve on retry.
OAuth consent query failure (429 rate limiting): too many Graph API calls in a short window. The playbook retries after the delay specified in the Retry-After header (typically 10 seconds). If the retry also fails, the playbook logs the failure and continues. OAuth evidence can be collected manually if automated collection misses it.
Mailbox rule enumeration failure (404): the user's mailbox may not exist in Exchange Online (shared mailbox not yet provisioned, on-premises mailbox not synced). This is informational, not an error, some users do not have Exchange mailboxes.
The collection follows a "best effort, fully documented" principle: collect as much as possible, document every failure with the specific error, and never block the entire pipeline because one source fails. Partial evidence with documented gaps is more useful than no evidence because one query failed.
How identity collection feeds the response pipeline
The identity snapshot is not a standalone artifact. It feeds three downstream processes, and the snapshot format is designed to support all three without manual reformatting.
The integration between SA3 and SA5 is the tightest coupling in the SA architecture. If the snapshot format changes: a new field name, a different ID format, a restructured JSON response from a Graph API update, containment breaks. The playbook specification in Section 3.11 defines the interface contract between the two: the exact JSON structure, the field names SA5 reads, and the flag format SA5 parses. Changing one side without updating the other is how automated containment removes a legitimate MFA method instead of the attacker's.