In this module
IR1.6 Cloud Investigation Tools — KQL, Purview, Defender XDR
Cloud Investigation Tools
No installation required — but you need to know which portal answers which question
Endpoint forensics requires downloading, installing, and configuring tools before you can analyze anything. Cloud investigation is different — the tools are already there. KQL queries run in the Defender XDR portal. Audit logs live in Purview. Sign-in analysis happens in Entra ID. The challenge is not installation — it is knowing which of the three investigation interfaces to use for each question, and how to write the queries that extract the evidence you need.
Unlike endpoint forensics, where you install tools on your forensic workstation and analyze collected artifacts, cloud investigation uses browser-based portals and query interfaces that are already part of your M365 environment. Nothing to install. Nothing to deploy. The tools are available right now — you just need to know which portal to open, which table to query, and how long the data is retained.
# M365 Developer Tenant Setup
# 1. Navigate to https://developer.microsoft.com/en-us/microsoft-365/dev-program
# 2. Sign in with a Microsoft account (create one if needed)
# 3. Join the Developer Program (free)
# 4. Choose "Instant sandbox" — pre-configured tenant
# with 16 sample users and sample data
# 5. Record your credentials:
# Admin: admin@<yourname>.onmicrosoft.com
# Password: (set during setup)
#
# The instant sandbox includes:
# 16 users with mailboxes, OneDrive, and Teams
# Sample emails and documents pre-populated
# Simulated sign-in activity and audit log entries
# E5 licensing: Defender XDR, Purview Audit (premium),
# Entra ID P2, Defender for Endpoint, Cloud Apps, Identity
#
# Post-creation configuration:
# 1. Navigate to https://security.microsoft.com
# Verify Advanced Hunting is accessible
# 2. Navigate to https://purview.microsoft.com → Audit
# Verify audit log search returns data
# 3. Enable E5 audit events (may auto-enable):
Connect-ExchangeOnline -UserPrincipalName admin@yourtenant.onmicrosoft.com
Set-OrganizationConfig -AuditDisabled $false
Get-Mailbox -ResultSize Unlimited | Set-Mailbox -AuditEnabled $true
Disconnect-ExchangeOnline -Confirm:$false// Validation: confirm your Advanced Hunting access works
// Run each query and verify it returns data
// 1. Sign-in activity
SigninLogs
| where TimeGenerated > ago(7d)
| summarize Count = count() by UserPrincipalName, ResultType
| sort by Count desc
| take 10
// ResultType 0 = success. Anything else = failure/interruption.
// If this returns data, identity investigation is operational.
// 2. Email telemetry
EmailEvents
| where Timestamp > ago(7d)
| summarize Count = count() by SenderFromAddress
| sort by Count desc
| take 10
// If this returns data, email investigation is operational.
// 3. Device telemetry (if endpoints are onboarded)
DeviceProcessEvents
| where Timestamp > ago(7d)
| summarize Count = count() by DeviceName
| sort by Count desc
| take 5
// If this returns data, endpoint investigation via KQL is operational.// IDENTITY COMPROMISE: Sign-ins from unfamiliar locations
// Use after a phishing report to determine if credentials were used
// Adapt: change UPN and time window to match the incident
SigninLogs
| where TimeGenerated > ago(24h)
| where UserPrincipalName == "jmorrison@northgateeng.com"
| where ResultType == 0 // Successful sign-ins only
| project TimeGenerated, UserPrincipalName, IPAddress,
Location, DeviceDetail, UserAgent,
ConditionalAccessStatus, RiskLevelDuringSignIn,
AuthenticationRequirement, MfaDetail
| sort by TimeGenerated desc
// INTERPRETATION:
// Look for: unfamiliar IPs, unexpected countries, new device details,
// risk level "high" or "medium", MFA not satisfied (token replay),
// UserAgent strings that don't match the user's known browser/OS// EMAIL SCOPE: What did the compromised mailbox receive?
// Use after confirming identity compromise to determine email exposure
EmailEvents
| where Timestamp > ago(7d)
| where RecipientEmailAddress == "jmorrison@northgateeng.com"
| where DeliveryAction == "Delivered"
| project Timestamp, SenderFromAddress, Subject,
DeliveryAction, AttachmentCount, UrlCount, ThreatTypes
| sort by Timestamp desc
// Cross-reference with Purview MailItemsAccessed to determine
// which delivered emails the attacker actually read// SCOPE DETERMINATION: All accounts that clicked the phishing URL
// Use when you have identified the specific phishing URL
EmailUrlInfo
| where Timestamp > ago(7d)
| where Url contains "malicious-phishing-domain.com"
| join kind=inner (
EmailEvents
| where DeliveryAction == "Delivered"
) on NetworkMessageId
| distinct RecipientEmailAddress
// EACH RESULT = a potentially compromised account
// Run the identity compromise query for EACH of these accounts
// This is how one phishing email becomes a 12-account investigation// INBOX RULE DETECTION: Find attacker-created forwarding rules
// Use after identity compromise to check for persistent email access
// Inbox rules survive password reset — the attacker maintains access
// via forwarding even after credentials are changed
CloudAppEvents
| where Timestamp > ago(30d)
| where ActionType == "New-InboxRule"
| where AccountObjectId == "<compromised user object ID>"
| project Timestamp, AccountDisplayName,
RawEventData.Parameters
| sort by Timestamp desc
// Look for: rules that forward to external addresses,
// rules that delete emails matching specific criteria,
// rules with suspicious names (".", "Update", " ")# Validate Purview Audit access
Connect-ExchangeOnline -UserPrincipalName admin@yourtenant.onmicrosoft.com
# Search for MailItemsAccessed events for the compromised user
$results = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -UserIds "jmorrison@northgateeng.com" -Operations MailItemsAccessed -ResultSize 100
# Examine the results
$results | ForEach-Object {
$auditData = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Timestamp = $auditData.CreationTime
User = $auditData.UserId
Operation = $auditData.Operation
ClientIP = $auditData.ClientIPAddress
MailboxOwner = $auditData.MailboxOwnerUPN
ItemCount = ($auditData.Folders | ForEach-Object { $_.FolderItems.Count } | Measure-Object -Sum).Sum
}
}
Disconnect-ExchangeOnline -Confirm:$false# Sentinel setup checklist (portal-based configuration):
#
# 1. Create a Log Analytics workspace (if not already existing)
# Azure Portal → Create a resource → Log Analytics workspace
# Region: same as your M365 tenant for lowest latency
#
# 2. Add Microsoft Sentinel to the workspace
# Azure Portal → Microsoft Sentinel → Add → select your workspace
#
# 3. Enable data connectors (Sentinel → Data connectors):
# Microsoft 365 (Exchange, SharePoint, Teams activity)
# → Provides: OfficeActivity table
# Microsoft Entra ID (sign-in logs, audit logs, provisioning)
# → Provides: SigninLogs, AuditLogs tables
# Microsoft Defender XDR (incidents, alerts, device events)
# → Provides: SecurityIncident, SecurityAlert, Device* tables
# Azure Activity (Azure management operations)
# → Provides: AzureActivity table
#
# 4. Verify data flow// Sentinel validation: check which tables have data
search *
| where TimeGenerated > ago(24h)
| distinct $table
| sort by $table asc
// Expected for a well-configured Sentinel:
// AuditLogs, AzureActivity, OfficeActivity, SecurityAlert,
// SecurityIncident, SigninLogs, and Device* tables if
// Defender XDR connector is enabled
// Verify sign-in data is flowing
SigninLogs
| where TimeGenerated > ago(24h)
| summarize Count = count()
// Any count > 0 confirms Entra ID data is reaching Sentinel# Evidence preservation: export audit logs to case folder
# Run this IMMEDIATELY when an incident is detected
$caseId = "INC-NE-2026-0315-001"
$outputPath = "C:\IR\Cases\$caseId\Evidence\AuditLogs"
New-Item -ItemType Directory -Path $outputPath -Force
Connect-ExchangeOnline -UserPrincipalName admin@yourtenant.onmicrosoft.com
# Export all audit events for the compromised user
# Adjust date range to cover the full compromise window
$startDate = "2026-03-01"
$endDate = "2026-03-16"
$userId = "jmorrison@northgateeng.com"
$allResults = @()
$sessionId = "AuditExport_$(Get-Date -Format 'yyyyMMddHHmm')"
do {
$batch = Search-UnifiedAuditLog -StartDate $startDate -EndDate $endDate -UserIds $userId -SessionId $sessionId -SessionCommand ReturnLargeSet -ResultSize 5000
$allResults += $batch
Write-Host "Retrieved $($allResults.Count) records..." -ForegroundColor Cyan
} while ($batch.Count -eq 5000)
$allResults | Export-Csv "$outputPath\audit_export_$($userId.Replace('@','_')).csv" -NoTypeInformation
Write-Host "Exported $($allResults.Count) audit records to $outputPath" -ForegroundColor Green
Disconnect-ExchangeOnline -Confirm:$false# Live Response capabilities (commands available in the session):
#
# File operations:
# fileinfo <path> — file metadata (size, hash, timestamps)
# getfile <path> — download a file from the endpoint
# putfile <filename> — upload a file TO the endpoint
# findfile <name> — search for files by name
#
# Process operations:
# processes — list running processes
# connections — list network connections
#
# Script execution:
# run <script.ps1> — execute a pre-uploaded PowerShell script
# library — list available scripts in the library
#
# Evidence collection:
# remediate file <path> — quarantine a malicious file
# collect <file> — collect a file for analysis
#
# Analysis:
# analyze file <path> — submit file for cloud analysis
# trace <pid> — start/stop ETW trace on a process// WORKED EXAMPLE: Build the complete picture of a compromised account
// This query joins identity, email, and cloud tables to show
// everything the attacker did after authentication
// Step 1: Find the suspicious sign-in
let compromisedUser = "jmorrison@northgateeng.com";
let attackStartTime = datetime(2026-03-15T14:29:00Z);
let attackEndTime = datetime(2026-03-15T16:00:00Z);
// Step 2: What emails arrived during the attack window?
EmailEvents
| where Timestamp between (attackStartTime .. attackEndTime)
| where RecipientEmailAddress == compromisedUser
| where DeliveryAction == "Delivered"
| project Timestamp, SenderFromAddress, Subject, AttachmentCount
| sort by Timestamp asc
// Step 3: What cloud app activity occurred?
// Run separately — joins across domains can be complex
CloudAppEvents
| where Timestamp between (attackStartTime .. attackEndTime)
| where AccountObjectId == "<user object ID>"
| project Timestamp, ActionType, Application, ActivityObjects
| sort by Timestamp asc
// These two queries together show: what the attacker received
// in email AND what they accessed in cloud apps. Combine with
// SigninLogs from the identity query to build the full timeline.SigninLogs | where UserPrincipalName == "...") or SPL (index=azure_ad sourcetype=azure:aad:signin user="..."). The data is identical — Microsoft generates it. The query language is the delivery mechanism. If you can follow the investigation reasoning, you can translate the queries to any SIEM. IR12 includes a cross-reference table for the most common IR queries in both KQL and SPL.Build it: Validate all three cloud investigation interfaces
Run the three validation queries in Defender XDR Advanced Hunting
Run the three validation queries in Defender XDR Advanced Hunting. Confirm SigninLogs and EmailEvents return data. Connect to Exchange Online via PowerShell and run a Purview Audit search. If Sentinel is configured, verify data flow. Save the investigation-ready KQL queries to your Advanced Hunting library. Each successful validation confirms one cloud evidence source for the investigation modules in Phase 3. If any interface fails, troubleshoot before proceeding — Phase 3 depends on these being operational.
Beyond this investigation
The techniques taught in this subsection apply beyond the specific scenario presented here. The same evidence sources, tools, and analytical methods are used across ransomware, BEC, insider threat, and APT investigations — the context changes but the methodology is consistent.
The investigation reveals the attacker accessed the compromised user's email for 5 days. The user's mailbox contains emails with supplier pricing, employee salaries, and a board presentation. The CISO asks: 'What did the attacker read?' Can you answer definitively?
Only if MailItemsAccessed auditing is enabled (requires E5 + advanced auditing). If enabled: query OfficeActivity for MailItemsAccessed (Bind) events from the attacker's IP — this shows exactly which emails were opened. If NOT enabled: you can only confirm the attacker had ACCESS to the mailbox for 5 days, not which specific emails they read. The IR report must distinguish between 'accessed' (confirmed) and 'read' (confirmed only with MailItemsAccessed). Overstating the finding ('the attacker read all emails') when the evidence shows only 'the attacker had mailbox access' is a forensic accuracy failure.