Detection rules › Kusto
Whisper Security - SPF Record Unauthorized Include Detection
Detects new SPF include directives on monitored domains that were not seen in the previous scan window. Unauthorized SPF modifications may allow adversaries to send phishing emails that pass SPF checks.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule body
id: 9c275139-b554-58f1-b390-8520b10e54ad
name: Whisper Security - SPF Record Unauthorized Include Detection
description: |
Detects new SPF include directives on monitored domains that were not seen in the previous scan window. Unauthorized SPF modifications may allow adversaries to send phishing emails that pass SPF checks.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
dataTypes:
- WhisperInfraContext_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
// MITRE ATT&CK: T1566.001 - Phishing: Spearphishing Attachment
// Tactic: Initial Access
// Detects unauthorized SPF include directives not seen in previous scans
// Configure authorized_spf_includes with your organization's known SPF providers
let authorized_spf_includes = dynamic(["_spf.google.com", "spf.protection.outlook.com", "amazonses.com", "sendgrid.net", "mailgun.org"]);
let previousIncludes = WhisperInfraContext_CL
| where TimeGenerated between (ago(1d) .. ago(1h))
| where isnotempty(spfIncludes)
| mv-expand SpfInclude = split(spfIncludes, ",")
| extend SpfInclude = tostring(SpfInclude)
| summarize PreviousIncludes = make_set(SpfInclude) by indicator;
let currentIncludes = WhisperInfraContext_CL
| where TimeGenerated > ago(1h)
| where isnotempty(spfIncludes)
| mv-expand SpfInclude = split(spfIncludes, ",")
| extend SpfInclude = tostring(SpfInclude)
| project indicator, SpfInclude, TimeGenerated;
currentIncludes
| join kind=leftanti (previousIncludes | mv-expand PrevInclude = PreviousIncludes | extend SpfInclude = tostring(PrevInclude) | project indicator, SpfInclude) on indicator, SpfInclude
| where SpfInclude !in (authorized_spf_includes)
| project TimeGenerated, DnsDomain = indicator, UnauthorizedInclude = SpfInclude
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DnsDomain
customDetails:
Domain: DnsDomain
UnauthorizedInclude: UnauthorizedInclude
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let authorized_spf_includes = dynamic(["_spf.google.com", "spf.protection.outlook.com", "amazonses.com", "sendgrid.net", "mailgun.org"]);
let currentIncludes is inlined into the numbered stages below.
Let binding: previousIncludes
let previousIncludes = WhisperInfraContext_CL
| where TimeGenerated between (ago(1d) .. ago(1h))
| where isnotempty(spfIncludes)
| mv-expand SpfInclude = split(spfIncludes, ",")
| extend SpfInclude = tostring(SpfInclude)
| summarize PreviousIncludes = make_set(SpfInclude) by indicator;
Stages 1 to 6 define let currentIncludes (the rule's main pipeline source); stages 7 to 10 run on it.
Stage 1: source
WhisperInfraContext_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where isnotempty(spfIncludes)
Stage 4: mv-expand
| mv-expand SpfInclude = split(spfIncludes, ",")
Stage 5: extend
| extend SpfInclude = tostring(SpfInclude)
Stage 6: project
| project indicator, SpfInclude, TimeGenerated
Stage 7: join (negated)
currentIncludes
| join kind=leftanti (previousIncludes | mv-expand PrevInclude = PreviousIncludes | extend SpfInclude = tostring(PrevInclude) | project indicator, SpfInclude) on indicator, SpfInclude
Stage 8: where
| where SpfInclude !in (authorized_spf_includes)
Stage 9: project
| project TimeGenerated, DnsDomain = indicator, UnauthorizedInclude = SpfInclude
Stage 10: summarize aggregation inside the join branch
summarize by indicator
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
spfIncludes | is_not_null | excludes:spfIncludes | |
SpfInclude | in | _spf.google.com, amazonses.com, mailgun.org, sendgrid.net, spf.protection.outlook.com | excludes:SpfInclude |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
spfIncludes | is_not_null | field:"spfIncludes" kind:is_not_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
indicator | summarize |