Detection rules › Kusto

Mail redirect via ExO transport rule

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Identifies when Exchange Online transport rule configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts.

MITRE ATT&CK coverage

TacticTechniques
CollectionT1114 Email Collection
ExfiltrationT1020 Automated Exfiltration

Event coverage

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body kusto

id: 500415fb-bba7-4227-a08a-9857fb61b6a7
name: Mail redirect via ExO transport rule
description: |
  'Identifies when Exchange Online transport rule configured to forward emails.
  This could be an adversary mailbox configured to collect mail from multiple user accounts.'
severity: Medium
status: Available 
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (Exchange)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - Exfiltration
relevantTechniques:
  - T1114
  - T1020
query: |
  OfficeActivity
  | where OfficeWorkload == "Exchange"
  | where Operation in~ ("New-TransportRule", "Set-TransportRule")
  | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))
  | extend RuleName = case(
      Operation =~ "Set-TransportRule", OfficeObjectId,
      Operation =~ "New-TransportRule", ParsedParameters.Name,
      "Unknown")
  | mv-expand ExpandedParameters = todynamic(Parameters)
  | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
  | extend RedirectTo = ExpandedParameters.Value
  | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
  | extend From = ParsedParameters.From
  | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters
  | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 2.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

OfficeActivity

Stage 2: where

| where OfficeWorkload == "Exchange"

Stage 3: where

| where Operation in~ ("New-TransportRule", "Set-TransportRule")

Stage 4: kusto:mv-apply

| mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))

Stage 5: extend

| extend RuleName = case(
    Operation =~ "Set-TransportRule", OfficeObjectId,
    Operation =~ "New-TransportRule", ParsedParameters.Name,
    "Unknown")
RuleName =
ifOperation =~ "Set-TransportRule"OfficeObjectId
elifOperation =~ "New-TransportRule"ParsedParameters.Name
else"Unknown"

Stage 6: mv-expand

| mv-expand ExpandedParameters = todynamic(Parameters)

Stage 7: where

| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)

Stage 8: extend (3 consecutive steps)

| extend RedirectTo = ExpandedParameters.Value
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
| extend From = ParsedParameters.From

Stage 9: project

| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters

Stage 10: extend

| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])

Stage 11: summarize

summarize

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.

FieldKindValues
Namein
  • BlindCopyTo
  • RedirectMessageTo
OfficeWorkloadeq
  • Exchange transforms: cased
Operationin
  • New-TransportRule
  • Set-TransportRule
Valueis_not_null
  • (no value, null check)