GSA Enriched Office 365 - Malicious Inbox Rule
Id | a9c76c8d-f60d-49ec-9b1f-bdfee6db3807 |
Rulename | GSA Enriched Office 365 - Malicious Inbox Rule |
Description | Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords. This is done so as to limit ability to warn compromised users that they’ve been compromised. Below is a sample query that tries to detect this. Reference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/ |
Severity | Medium |
Tactics | Persistence DefenseEvasion |
Techniques | T1098 T1078 |
Required data connectors | AzureActiveDirectory Office365 |
Kind | Scheduled |
Query frequency | 1d |
Query period | 1d |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml |
Version | 2.0.6 |
Arm template | a9c76c8d-f60d-49ec-9b1f-bdfee6db3807.json |
let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]);
// OfficeActivity Query
let OfficeEvents = OfficeActivity
| where OfficeWorkload =~ "Exchange"
| where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded")
| where Parameters has "Deleted Items" or Parameters has "Junk Email" or Parameters has "DeleteMessage"
| extend Events = todynamic(Parameters)
| parse Events with * "SubjectContainsWords" SubjectContainsWords '}'*
| parse Events with * "BodyContainsWords" BodyContainsWords '}'*
| parse Events with * "SubjectOrBodyContainsWords" SubjectOrBodyContainsWords '}'*
| where SubjectContainsWords has_any (Keywords)
or BodyContainsWords has_any (Keywords)
or SubjectOrBodyContainsWords has_any (Keywords)
| extend ClientIPAddress = case(
ClientIP has ".", tostring(split(ClientIP, ":")[0]),
ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0]))),
ClientIP
)
| extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))
| extend RuleDetail = case(
OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]),
OfficeObjectId contains '\\', tostring(split(OfficeObjectId, '\\')[-1]),
"Unknown"
)
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
| extend OriginatingServerName = tostring(split(OriginatingServer, " ")[0]);
// EnrichedMicrosoft365AuditLogs Query
let EnrichedEvents = EnrichedMicrosoft365AuditLogs
| where Workload =~ "Exchange"
| where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded")
| where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Deleted Items"
or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Junk Email"
or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "DeleteMessage"
| extend Events = parse_json(tostring(AdditionalProperties)).Parameters
| extend SubjectContainsWords = tostring(Events.SubjectContainsWords),
BodyContainsWords = tostring(Events.BodyContainsWords),
SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)
| where SubjectContainsWords has_any (Keywords)
or BodyContainsWords has_any (Keywords)
or SubjectOrBodyContainsWords has_any (Keywords)
| extend ClientIPAddress = case(
ClientIp has ".", tostring(split(ClientIp, ":")[0]),
ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))),
ClientIp
)
| extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))
| extend RuleDetail = case(
ObjectId contains '/', tostring(split(ObjectId, '/')[-1]),
ObjectId contains '\\', tostring(split(ObjectId, '\\')[-1]),
"Unknown"
)
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]);
// Combine and Deduplicate
let CombinedEvents = OfficeEvents
| union EnrichedEvents
| summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress;
// Final Output
CombinedEvents
| project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix;
id: a9c76c8d-f60d-49ec-9b1f-bdfee6db3807
tactics:
- Persistence
- DefenseEvasion
queryPeriod: 1d
triggerThreshold: 0
name: GSA Enriched Office 365 - Malicious Inbox Rule
query: |
let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]);
// OfficeActivity Query
let OfficeEvents = OfficeActivity
| where OfficeWorkload =~ "Exchange"
| where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded")
| where Parameters has "Deleted Items" or Parameters has "Junk Email" or Parameters has "DeleteMessage"
| extend Events = todynamic(Parameters)
| parse Events with * "SubjectContainsWords" SubjectContainsWords '}'*
| parse Events with * "BodyContainsWords" BodyContainsWords '}'*
| parse Events with * "SubjectOrBodyContainsWords" SubjectOrBodyContainsWords '}'*
| where SubjectContainsWords has_any (Keywords)
or BodyContainsWords has_any (Keywords)
or SubjectOrBodyContainsWords has_any (Keywords)
| extend ClientIPAddress = case(
ClientIP has ".", tostring(split(ClientIP, ":")[0]),
ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0]))),
ClientIP
)
| extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))
| extend RuleDetail = case(
OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]),
OfficeObjectId contains '\\', tostring(split(OfficeObjectId, '\\')[-1]),
"Unknown"
)
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
| extend OriginatingServerName = tostring(split(OriginatingServer, " ")[0]);
// EnrichedMicrosoft365AuditLogs Query
let EnrichedEvents = EnrichedMicrosoft365AuditLogs
| where Workload =~ "Exchange"
| where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded")
| where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Deleted Items"
or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Junk Email"
or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "DeleteMessage"
| extend Events = parse_json(tostring(AdditionalProperties)).Parameters
| extend SubjectContainsWords = tostring(Events.SubjectContainsWords),
BodyContainsWords = tostring(Events.BodyContainsWords),
SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)
| where SubjectContainsWords has_any (Keywords)
or BodyContainsWords has_any (Keywords)
or SubjectOrBodyContainsWords has_any (Keywords)
| extend ClientIPAddress = case(
ClientIp has ".", tostring(split(ClientIp, ":")[0]),
ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))),
ClientIp
)
| extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))
| extend RuleDetail = case(
ObjectId contains '/', tostring(split(ObjectId, '/')[-1]),
ObjectId contains '\\', tostring(split(ObjectId, '\\')[-1]),
"Unknown"
)
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]);
// Combine and Deduplicate
let CombinedEvents = OfficeEvents
| union EnrichedEvents
| summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress;
// Final Output
CombinedEvents
| project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix;
severity: Medium
triggerOperator: gt
kind: Scheduled
relevantTechniques:
- T1098
- T1078
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml
queryFrequency: 1d
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Exchange)
description: |
Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.
This is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.
Reference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/
status: Available
version: 2.0.6
entityMappings:
- fieldMappings:
- columnName: UserId
identifier: FullName
- columnName: AccountName
identifier: Name
- columnName: AccountUPNSuffix
identifier: UPNSuffix
entityType: Account
- fieldMappings:
- columnName: ClientIPAddress
identifier: Address
entityType: IP
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"apiVersion": "2024-01-01-preview",
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')]",
"properties": {
"alertRuleTemplateName": "a9c76c8d-f60d-49ec-9b1f-bdfee6db3807",
"customDetails": null,
"description": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/\n",
"displayName": "GSA Enriched Office 365 - Malicious Inbox Rule",
"enabled": true,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "UserId",
"identifier": "FullName"
},
{
"columnName": "AccountName",
"identifier": "Name"
},
{
"columnName": "AccountUPNSuffix",
"identifier": "UPNSuffix"
}
]
},
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "ClientIPAddress",
"identifier": "Address"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml",
"query": "let Keywords = dynamic([\"helpdesk\", \"alert\", \"suspicious\", \"fake\", \"malicious\", \"phishing\", \"spam\", \"do not click\", \"do not open\", \"hijacked\", \"Fatal\"]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where Parameters has \"Deleted Items\" or Parameters has \"Junk Email\" or Parameters has \"DeleteMessage\"\n | extend Events = todynamic(Parameters)\n | parse Events with * \"SubjectContainsWords\" SubjectContainsWords '}'*\n | parse Events with * \"BodyContainsWords\" BodyContainsWords '}'*\n | parse Events with * \"SubjectOrBodyContainsWords\" SubjectOrBodyContainsWords '}'*\n | where SubjectContainsWords has_any (Keywords)\n or BodyContainsWords has_any (Keywords)\n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIP has \".\", tostring(split(ClientIP, \":\")[0]),\n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))),\n ClientIP\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]),\n OfficeObjectId contains '\\\\', tostring(split(OfficeObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend OriginatingServerName = tostring(split(OriginatingServer, \" \")[0]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Deleted Items\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Junk Email\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"DeleteMessage\"\n | extend Events = parse_json(tostring(AdditionalProperties)).Parameters\n | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), \n BodyContainsWords = tostring(Events.BodyContainsWords), \n SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)\n | where SubjectContainsWords has_any (Keywords) \n or BodyContainsWords has_any (Keywords) \n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]),\n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n ObjectId contains '/', tostring(split(ObjectId, '/')[-1]),\n ObjectId contains '\\\\', tostring(split(ObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress;\n\n// Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix;\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"severity": "Medium",
"status": "Available",
"subTechniques": [],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"DefenseEvasion",
"Persistence"
],
"techniques": [
"T1078",
"T1098"
],
"templateVersion": "2.0.6",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}