Hunting Playbook: Credential Theft in Microsoft Defender XDR.
The password was correct.
The sign-in succeeded.
MFA may even have completed successfully.
But twenty minutes later, the same account was accessing mail, cloud apps and resources from activity that did not fit the user. Agent Foskett knew this was not a password problem anymore. It was a credential theft investigation.
Lesson overview
Learn how to hunt for credential theft by reviewing sign-in telemetry, authentication patterns, risky users, IP addresses, token activity, mailbox access and related Microsoft Defender XDR evidence.
Why credential theft matters
The credential theft hunting workflow
Step 1 — Review recent sign-ins for the account
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let TimeFrame = 7d;
let UserAccount = "user@contoso.com";
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ UserAccount
| project Timestamp,
AccountUpn,
ActionType,
IPAddress,
Location,
DeviceName,
FailureReason
| order by Timestamp descStep 2 — Summarise sign-ins by IP address and location
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 14d;
let UserAccount = "user@contoso.com";
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ UserAccount
| summarize SignInCount = count(),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 20),
Outcomes = make_set(ActionType, 20)
by AccountUpn,
IPAddress,
Location
| order by LastSeen descStep 3 — Look for successful sign-ins after repeated failures
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let TimeFrame = 7d;
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| summarize FailedSignIns = countif(ActionType has "Failed"),
SuccessfulSignIns = countif(ActionType has "Success"),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Locations = make_set(Location, 20)
by AccountUpn,
IPAddress
| where FailedSignIns > 5 and SuccessfulSignIns > 0
| order by FailedSignIns descStep 4 — Identify accounts using many IP addresses
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let TimeFrame = 7d;
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where ActionType has "Success"
| summarize UniqueIPs = dcount(IPAddress),
IPAddresses = make_set(IPAddress, 50),
Locations = make_set(Location, 50),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by AccountUpn
| where UniqueIPs >= 5
| order by UniqueIPs descStep 5 — Pivot into cloud application activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 7d;
let UserAccount = "user@contoso.com";
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where AccountId =~ UserAccount or AccountDisplayName =~ UserAccount
| project Timestamp,
AccountId,
AccountDisplayName,
Application,
ActionType,
IPAddress,
DeviceType,
RawEventData
| order by Timestamp descStep 6 — Check for related alerts and evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let TimeFrame = 7d;
let UserAccount = "user@contoso.com";
AlertEvidence
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ UserAccount
or AccountName =~ UserAccount
| project Timestamp,
AlertId,
Title,
EntityType,
AccountUpn,
AccountName,
DeviceName,
RemoteIP,
EvidenceDirection
| order by Timestamp descStep 7 — Build a credential theft timeline
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
let TimeFrame = 7d;
let UserAccount = "user@contoso.com";
let SignIns =
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ UserAccount
| project Timestamp,
Source = "IdentityLogonEvents",
Event = ActionType,
Detail = strcat(IPAddress, " | ", Location),
Account = AccountUpn;
let CloudActivity =
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where AccountId =~ UserAccount or AccountDisplayName =~ UserAccount
| project Timestamp,
Source = "CloudAppEvents",
Event = ActionType,
Detail = tostring(Application),
Account = coalesce(AccountId, AccountDisplayName);
let Alerts =
AlertEvidence
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ UserAccount or AccountName =~ UserAccount
| project Timestamp,
Source = "AlertEvidence",
Event = Title,
Detail = EntityType,
Account = coalesce(AccountUpn, AccountName);
union SignIns, CloudActivity, Alerts
| order by Timestamp ascCommon false positives
Evidence that increases suspicion
Investigation checklist
Related Agent Foskett Academy lessons
Coming next
Final thought
Hunting Playbook Credential Theft in Microsoft Defender XDR
Agent Foskett Academy Lesson 102 teaches defenders how to hunt for credential theft using Microsoft Defender XDR, Microsoft Entra ID telemetry, sign-in behaviour, authentication events, cloud application activity and KQL investigation workflows.
Investigate credential theft with KQL and Microsoft Defender XDR
This lesson explains how to review suspicious sign-ins, compare IP addresses and locations, detect failed attempts before success, pivot into CloudAppEvents and build a credential theft investigation timeline.
Microsoft Entra ID credential theft investigation playbook
Defenders can use IdentityLogonEvents, CloudAppEvents, AlertEvidence, sign-in summaries, location analysis and Defender XDR correlation to investigate stolen credentials and compromised accounts.
