15.8 Detection Engineering
3-5 hours · Module 15
Detection Engineering
Five analytics rules that detect consent phishing patterns. These complement the M11-M13 detection packs.
Required role: Microsoft Sentinel Contributor.
Rule 1: Consent to High-Risk Permissions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Scheduled Rule: OAuth consent granting high-risk permissions
// Schedule: 15 minutes / Lookback: 20 minutes
// Severity: High
// MITRE: T1550.001 (Application Access Token)
AuditLogs
| where OperationName == "Consent to application"
| where Result == "success"
| extend AppName = tostring(TargetResources[0].displayName)
| extend ConsentUser = tostring(InitiatedBy.user.userPrincipalName)
| extend Permissions = tostring(TargetResources[0].modifiedProperties)
| where Permissions has_any ("Mail.ReadWrite", "Mail.Send",
"Files.ReadWrite.All", "Directory.ReadWrite.All",
"MailboxSettings.ReadWrite", "User.ReadWrite.All")
| extend IPAddress = tostring(InitiatedBy.user.ipAddress)
| project TimeGenerated, ConsentUser, AppName, Permissions, IPAddress
|
Entity mapping: Account → ConsentUser (FullName). IP → IPAddress (Address).
Rule 2: Consent from Non-Corporate IP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Scheduled Rule: OAuth consent from external IP — possible phishing
// Schedule: 15 minutes / Lookback: 20 minutes
// Severity: Medium
// MITRE: T1550.001
let KnownCorpIPs = _GetWatchlist('CorporateExternalIPs') | project SearchKey;
AuditLogs
| where OperationName == "Consent to application"
| where Result == "success"
| extend ConsentUser = tostring(InitiatedBy.user.userPrincipalName)
| extend IPAddress = tostring(InitiatedBy.user.ipAddress)
| where IPAddress !in (KnownCorpIPs)
| where isnotempty(IPAddress)
| extend AppName = tostring(TargetResources[0].displayName)
| project TimeGenerated, ConsentUser, AppName, IPAddress
|
Rule 3: Bulk Consent — Multiple Users to Same Application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Scheduled Rule: Multiple users consenting to the same app in a short window
// Schedule: 1 hour / Lookback: 4 hours
// Severity: High
// MITRE: T1550.001
AuditLogs
| where TimeGenerated > ago(4h)
| where OperationName == "Consent to application"
| where Result == "success"
| extend AppName = tostring(TargetResources[0].displayName)
| extend AppId = tostring(TargetResources[0].id)
| extend ConsentUser = tostring(InitiatedBy.user.userPrincipalName)
| summarize
UserCount = dcount(ConsentUser),
Users = make_set(ConsentUser, 20),
FirstConsent = min(TimeGenerated),
LastConsent = max(TimeGenerated)
by AppName, AppId
| where UserCount > 3
| project AppName, AppId, UserCount, Users, FirstConsent, LastConsent
|
What this detects: Consent phishing campaigns. More than 3 users consenting to the same application in 4 hours is almost always a phishing campaign — not organic adoption.
Rule 4: Newly Registered Application with Consent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Scheduled Rule: Consent to application created in the last 7 days
// Schedule: 1 hour / Lookback: 2 hours
// Severity: Medium
// MITRE: T1098.003 (Additional Cloud Credentials)
let NewApps = AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName == "Add application"
| extend AppId = tostring(TargetResources[0].id)
| project AppCreated = TimeGenerated, AppId;
AuditLogs
| where OperationName == "Consent to application"
| where Result == "success"
| extend AppId = tostring(TargetResources[0].id)
| extend AppName = tostring(TargetResources[0].displayName)
| extend ConsentUser = tostring(InitiatedBy.user.userPrincipalName)
| join kind=inner NewApps on AppId
| extend DaysSinceCreation = datetime_diff('day', TimeGenerated, AppCreated)
| where DaysSinceCreation < 7
| project TimeGenerated, ConsentUser, AppName, AppId, DaysSinceCreation
|
Rule 5: Application API Access After Consent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Scheduled Rule: Application accesses Exchange within 1 hour of consent
// Schedule: 1 hour / Lookback: 2 hours
// Severity: High
// MITRE: T1528 (Steal Application Access Token)
let RecentConsents = AuditLogs
| where TimeGenerated > ago(2h)
| where OperationName == "Consent to application"
| where Result == "success"
| extend AppId = tostring(TargetResources[0].id)
| project ConsentTime = TimeGenerated, AppId;
AADServicePrincipalSignInLogs
| where ResultType == "0"
| where ResourceDisplayName in ("Microsoft Exchange Online", "Microsoft Graph",
"Office 365 SharePoint Online")
| join kind=inner RecentConsents on AppId
| where TimeGenerated between(ConsentTime .. (ConsentTime + 1h))
| project AppId, ConsentTime, AccessTime = TimeGenerated,
ResourceDisplayName, IPAddress,
MinutesAfterConsent = datetime_diff('minute', TimeGenerated, ConsentTime)
|
What this detects: An application that accesses data within 1 hour of receiving consent. Legitimate applications may be used days or weeks after consent. An application that starts accessing Exchange or Graph within minutes of consent is likely automated exfiltration — the attacker’s script starts harvesting data as soon as the consent is granted.
Deployment checklist
For each rule: create in Sentinel Analytics, configure entity mapping, set MITRE technique, test against historical data, enable, monitor.
Subsection artifact: 5 deployable consent phishing detection rules. Combined with M11 (8), M12 (6), and M13 (5): 24 detection rules total covering the complete M365 attack surface from credential theft through persistence and financial fraud.
Compliance mapping: NIST CSF DE.AE-2. ISO 27001 A.8.16.
Tuning consent detection rules for your environment
Consent phishing detection rules generate more false positives than AiTM or BEC rules because legitimate application consent is common. Tune before deploying:
Rule 1 (High-Risk Permissions) tuning: Maintain an exclusion list of known legitimate applications that request high-risk permissions. Examples: your helpdesk tool (may request Mail.ReadWrite to manage tickets), your backup solution (may request Files.ReadWrite.All), your SSO provider (may request Directory.Read.All). Add these AppIds to the rule’s exclusion filter.
Rule 3 (Bulk Consent) tuning: Adjust the threshold based on your organisation’s application adoption patterns. If IT regularly deploys new applications and instructs 20 users to consent simultaneously: the threshold of 3 users in 4 hours generates false positives. Raise to 5 or 10 users. Alternatively: create an exclusion for consents that occur within 1 hour of an IT announcement email (correlate with EmailEvents from IT distribution lists).
Rule 4 (New Application) tuning: Exclude applications registered by your IT team’s admin accounts. Internal applications are new by definition — the rule should only flag consents to applications registered in external tenants.
1
2
3
4
5
6
7
8
9
10
| // Identify your top consented applications for exclusion tuning
AuditLogs
| where TimeGenerated > ago(90d)
| where OperationName == "Consent to application"
| where Result == "success"
| extend AppName = tostring(TargetResources[0].displayName)
| extend AppId = tostring(TargetResources[0].id)
| summarize ConsentCount = count() by AppName, AppId
| order by ConsentCount desc
| take 20
|
The top 20 most-consented applications are almost certainly legitimate. Add them to your exclusion list. Any new application NOT in this list that receives a consent is worth investigating.
Try it yourself
Run the top-20 consented applications query against your tenant. Review each: do you recognise the application? Is the publisher verified? Are the permissions appropriate? This produces your initial exclusion list for the consent detection rules — deploy the rules with these exclusions from day one to reduce false positive noise.
What you should produce
A list of 10-20 AppIds to exclude from consent detection rules. Each with: application name, publisher, permissions, and your assessment (legitimate / investigate / remove). This is both a detection tuning exercise and the first step of your quarterly consent audit.