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
Stage 0: let
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 <inlined as stages below>;
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)
join kind=leftanti (previousIncludes) on indicator, SpfInclude
Stage 8: where
where not (SpfInclude in~ ("_spf.google.com", "amazonses.com", "mailgun.org", "sendgrid.net", "spf.protection.outlook.com"))
Stage 9: project
project DnsDomain, TimeGenerated, UnauthorizedInclude
Stage 10: summarize aggregation inside the join branch
summarize by indicator
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
spfIncludes | is_not_null | field:"spfIncludes" kind:is_not_null |
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 |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
indicator | summarize |