Hunting Playbook: OAuth Abuse in Microsoft Defender XDR.
The user changed their password.
MFA was reset. Sessions were revoked. The mailbox looked quiet.
Yet the attacker still had access.
The credentials were no longer the problem.
The user had granted consent to a malicious OAuth application, and the application still had permission to act.
Lesson overview
Learn how to hunt for OAuth abuse by investigating application consent, delegated permissions, service principals, token activity and Microsoft Entra ID evidence inside Microsoft Defender XDR.
Why OAuth abuse matters
The OAuth abuse hunting workflow
Step 1 — Review recent application consent activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 14d;
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where ActionType has_any ("Consent", "Add service principal", "Add app role assignment", "Grant")
| project Timestamp,
AccountDisplayName,
AccountObjectId,
ActionType,
Application,
AppId,
IPAddress,
UserAgent,
RawEventData
| order by Timestamp descStep 2 — Search for suspicious application names
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 30d;
let SuspiciousAppTerms = dynamic(["verify", "secure", "document", "invoice", "signature", "account", "mail", "backup"]);
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(Application)
| where Application has_any (SuspiciousAppTerms)
| project Timestamp,
AccountDisplayName,
ActionType,
Application,
AppId,
IPAddress,
UserAgent
| order by Timestamp descStep 3 — Pivot from the user to recent sign-ins
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 14d;
let TargetUser = "user@contoso.com";
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser
| project Timestamp,
AccountUpn,
ActionType,
IPAddress,
Location,
DeviceName,
Application,
FailureReason
| order by Timestamp descStep 4 — Summarise applications used by each account
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let TimeFrame = 30d;
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(Application)
| summarize EventCount = count(),
Applications = make_set(Application, 50),
AppIds = make_set(AppId, 50),
IPAddresses = make_set(IPAddress, 50),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by AccountDisplayName
| order by EventCount descStep 5 — Correlate consent with mailbox activity
- 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
let TimeFrame = 14d;
let TargetUser = "user@contoso.com";
let ConsentEvents =
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where AccountDisplayName =~ TargetUser
| where ActionType has_any ("Consent", "Grant", "Add app role assignment")
| project ConsentTime = Timestamp,
AccountDisplayName,
Application,
AppId,
ConsentIPAddress = IPAddress;
let MailEvents =
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where AccountDisplayName =~ TargetUser
| where ActionType has_any ("MailItemsAccessed", "MailItems", "Read", "Send")
| project MailTime = Timestamp,
AccountDisplayName,
ActionType,
Application,
IPAddress;
ConsentEvents
| join kind=leftouter MailEvents on AccountDisplayName
| where isnull(MailTime) or MailTime >= ConsentTime
| order by ConsentTime desc, MailTime descStep 6 — Build an OAuth abuse timeline
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
let TimeFrame = 14d;
let TargetUser = "user@contoso.com";
let IdentityEvents =
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser
| project Timestamp,
EventType = "IdentitySignIn",
Account = AccountUpn,
Detail = strcat(ActionType, " from ", tostring(IPAddress), " ", tostring(Location));
let AppEvents =
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where AccountDisplayName =~ TargetUser
| project Timestamp,
EventType = "CloudAppActivity",
Account = AccountDisplayName,
Detail = strcat(ActionType, " | App: ", tostring(Application), " | IP: ", tostring(IPAddress));
union IdentityEvents, AppEvents
| order by Timestamp ascCommon false positives
Real-world investigation
Investigation checklist
Related Agent Foskett Academy lessons
Coming next
Final thought
Hunting OAuth Abuse in Microsoft Defender XDR
Agent Foskett Academy Lesson 104 teaches defenders how to investigate OAuth abuse, malicious application consent, delegated permissions and persistent access in Microsoft Defender XDR and Microsoft Entra ID.
Microsoft Entra ID OAuth abuse investigation playbook
This hunting playbook explains how to review consent events, suspicious applications, permissions, token activity and cloud application telemetry during Microsoft 365 compromise investigations.
Learn to hunt malicious OAuth applications with KQL
Defenders can use KQL, CloudAppEvents, IdentityLogonEvents and Microsoft Defender XDR telemetry to detect malicious OAuth apps and remove attacker persistence.
