Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

GSA Enriched Office 365 - Malicious Inbox Rule

Back
Ida9c76c8d-f60d-49ec-9b1f-bdfee6db3807
RulenameGSA Enriched Office 365 - Malicious Inbox Rule
DescriptionOften 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/
SeverityMedium
TacticsPersistence
DefenseEvasion
TechniquesT1098
T1078
Required data connectorsAzureActiveDirectory
Office365
KindScheduled
Query frequency1d
Query period1d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml
Version2.0.6
Arm templatea9c76c8d-f60d-49ec-9b1f-bdfee6db3807.json
Deploy To Azure
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;
relevantTechniques:
- T1098
- T1078
name: GSA Enriched Office 365 - Malicious Inbox Rule
requiredDataConnectors:
- dataTypes:
  - EnrichedMicrosoft365AuditLogs
  connectorId: AzureActiveDirectory
- dataTypes:
  - OfficeActivity (Exchange)
  connectorId: Office365
entityMappings:
- fieldMappings:
  - identifier: FullName
    columnName: UserId
  - identifier: Name
    columnName: AccountName
  - identifier: UPNSuffix
    columnName: AccountUPNSuffix
  entityType: Account
- fieldMappings:
  - identifier: Address
    columnName: ClientIPAddress
  entityType: IP
triggerThreshold: 0
id: a9c76c8d-f60d-49ec-9b1f-bdfee6db3807
tactics:
- Persistence
- DefenseEvasion
version: 2.0.6
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml
queryPeriod: 1d
kind: Scheduled
queryFrequency: 1d
severity: Medium
status: Available
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/  
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;  
triggerOperator: gt
{
  "$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"
    }
  ]
}