Detection rules › Kusto
NRT Malicious Inbox Rule
'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/'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | T1078 Valid Accounts, T1098 Account Manipulation |
| Stealth | T1078 Valid Accounts |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
Rule body kusto
id: b79f6190-d104-4691-b7db-823e05980895
name: NRT 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
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
tactics:
- Persistence
- DefenseEvasion
relevantTechniques:
- T1098
- T1078
query: |
let Keywords = dynamic(["helpdesk", " alert", " suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]);
OfficeActivity
| where OfficeWorkload =~ "Exchange"
| 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]) , tostring(split(OfficeObjectId, '\\')[-1]))
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserId
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: OriginatingServer
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIPAddress
version: 1.0.2
kind: NRT
metadata:
source:
kind: Community
author:
name: Pete Bryan
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Parameters
let Keywords = dynamic(["helpdesk", " alert", " suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]);
Stage 1: source
OfficeActivity
Stage 2: where
| where OfficeWorkload =~ "Exchange"
Stage 3: where
| where Parameters has "Deleted Items" or Parameters has "Junk Email" or Parameters has "DeleteMessage"
Stage 4: extend
| extend Events=todynamic(Parameters)
Stage 5: parse
| parse Events with * "SubjectContainsWords" SubjectContainsWords '}'*
Stage 6: parse
| parse Events with * "BodyContainsWords" BodyContainsWords '}'*
Stage 7: parse
| parse Events with * "SubjectOrBodyContainsWords" SubjectOrBodyContainsWords '}'*
Stage 8: where
| where SubjectContainsWords has_any (Keywords)
or BodyContainsWords has_any (Keywords)
or SubjectOrBodyContainsWords has_any (Keywords)
Stage 9: extend (3 consecutive steps)
| 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]) , tostring(split(OfficeObjectId, '\\')[-1]))
ClientIPAddress =ClientIP has "."tostring(split(ClientIP, ":")[0])ClientIP has "["tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0])))ClientIPStage 10: summarize
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
BodyContainsWords | match |
|
OfficeWorkload | eq |
|
Parameters | match |
|
SubjectContainsWords | match |
|
SubjectOrBodyContainsWords | match |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
ClientIPAddress | summarize |
EndTimeUtc | summarize |
Keyword | summarize |
OfficeObjectId | summarize |
OriginatingServer | summarize |
ResultStatus | summarize |
RuleDetail | summarize |
StartTimeUtc | summarize |
UserId | summarize |