relevantTechniques:
- T1566
entityMappings:
- entityType: Account
fieldMappings:
- columnName: EmailFrom
identifier: FullName
- entityType: IP
fieldMappings:
- columnName: SrcIp
identifier: Address
- entityType: DNS
fieldMappings:
- columnName: RepresentativeUrlDomain
identifier: DomainName
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
suppressionDuration: PT1H
id: 8972b513-12a2-4b46-8263-3f091d88a8bc
suppressionEnabled: false
severity: Medium
kind: Scheduled
queryFrequency: 1h
description: |
'Detects email forensics events containing one or more URLs whose domain has not been seen in the previous 14 days, which may indicate newly observed phishing infrastructure or suspicious delivery patterns.'
requiredDataConnectors:
- connectorId: RedSiftPush
dataTypes:
- RedSiftEmailForensics_CL
triggerOperator: gt
name: Red Sift - Email with URL to previously unseen domain
tactics:
- InitialAccess
alertDetailsOverride:
alertDescriptionFormat: 'Email from {{EmailFrom}} contains {{NewDomainCount}} previously unseen URL domain(s): {{NewUrlDomainList}}.'
alertDisplayNameFormat: RedSift - URL to new domain in email from {{EmailFrom}}
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Red Sift/Analytic Rules/RedSiftEmailUrlWithNewDomain.yaml
triggerThreshold: 0
queryPeriod: 14d
query: |
let lookback = 14d;
let recentWindow = 1h;
let historicalDomains = RedSiftEmailForensics_CL
| extend EmailUrls = todynamic(EmailUrls)
| where TimeGenerated between (ago(lookback) .. ago(recentWindow))
| where isnotempty(EmailUrls) and array_length(EmailUrls) > 0
| mv-expand Url = EmailUrls
| extend UrlString = tostring(Url.url_string)
| where isnotempty(UrlString)
| extend UrlDomain = tostring(parse_url(UrlString).Host)
| where isnotempty(UrlDomain)
| summarize by UrlDomain;
RedSiftEmailForensics_CL
| extend
EmailFrom = tostring(column_ifexists("EmailFrom", "")),
EmailSubject = tostring(column_ifexists("EmailSubject", "")),
EmailReturnPath = tostring(column_ifexists("EmailReturnPath", "")),
EmailMessageUid = tostring(column_ifexists("EmailMessageUid", "")),
SrcIp = tostring(column_ifexists("SrcIp", "")),
DstHostname = tostring(column_ifexists("DstHostname", "")),
Severity = tostring(column_ifexists("Severity", "")),
Message = tostring(column_ifexists("Message", "")),
CorrelationUid = tostring(column_ifexists("CorrelationUid", "")),
EmailUrls = todynamic(EmailUrls)
| where TimeGenerated >= ago(recentWindow)
| where isnotempty(EmailUrls) and array_length(EmailUrls) > 0
| mv-expand Url = EmailUrls
| extend UrlString = tostring(Url.url_string)
| where isnotempty(UrlString)
| extend UrlDomain = tostring(parse_url(UrlString).Host)
| where isnotempty(UrlDomain)
| join kind=leftanti (historicalDomains) on UrlDomain
| summarize
NewUrlDomains = make_set(UrlDomain, 50),
NewUrls = make_set(UrlString, 50),
NewDomainCount = dcount(UrlDomain),
UrlCount = dcount(UrlString),
RepresentativeUrlDomain = take_any(UrlDomain)
by TimeGenerated,
EmailFrom,
EmailSubject,
EmailReturnPath,
EmailMessageUid,
SrcIp,
DstHostname,
Severity,
Message,
CorrelationUid
| extend
NewUrlDomainList = strcat_array(NewUrlDomains, ", "),
NewUrlList = strcat_array(NewUrls, ", ")
| project
TimeGenerated,
EmailFrom,
EmailSubject,
EmailReturnPath,
EmailMessageUid,
SrcIp,
DstHostname,
RepresentativeUrlDomain,
NewDomainCount,
UrlCount,
NewUrlDomainList,
NewUrlList,
Severity,
Message,
CorrelationUid
status: Available
customDetails:
ReturnPath: EmailReturnPath
CorrelationUid: CorrelationUid
EmailSubject: EmailSubject
NewDomainCount: NewDomainCount
NewUrlList: NewUrlList
NewUrlDomainList: NewUrlDomainList
incidentConfiguration:
createIncident: true
groupingConfiguration:
matchingMethod: Selected
groupByEntities:
- Account
- DNS
groupByCustomDetails:
- EmailSubject
reopenClosedIncident: false
enabled: true
lookbackDuration: P1D