TI Map URL Entity to EmailUrlInfo
| Id | 9e32e545-e60c-47de-9941-f9ca1ada0a42 |
| Rulename | TI Map URL Entity to EmailUrlInfo |
| Description | This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo. |
| Severity | Medium |
| Tactics | CommandAndControl |
| Techniques | T1071 |
| Required data connectors | AzureActiveDirectory MicrosoftDefenderThreatIntelligence ThreatIntelligence ThreatIntelligenceTaxii |
| Kind | Scheduled |
| Query frequency | 1h |
| Query period | 14d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Threat Intelligence (NEW)/Analytic Rules/URLEntity_EmailUrlInfo_Updated.yaml |
| Version | 1.0.4 |
| Arm template | 9e32e545-e60c-47de-9941-f9ca1ada0a42.json |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let EmailUrlInfo_ = materialize(EmailUrlInfo
| where TimeGenerated >= ago(dt_lookBack)
| extend EmailUrlInfo_TimeGenerated = TimeGenerated);
let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url);
let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain);
let EmailEvents_ = materialize(EmailEvents
| where TimeGenerated >= ago(dt_lookBack));
let TI = materialize(ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend Url = ObservableValue
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (EmailUrls) or tolower(Url) in (EmailUrlDomains)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil)));
(union
(TI | join kind=innerunique (EmailUrlInfo_) on Url),
(TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain))
| where EmailUrlInfo_TimeGenerated < ValidUntil
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId
| extend timestamp = EmailUrlInfo_TimeGenerated
| join kind=inner (EmailEvents_) on NetworkMessageId
| where DeliveryAction !has "Blocked"
| extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0])
queryPeriod: 14d
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let EmailUrlInfo_ = materialize(EmailUrlInfo
| where TimeGenerated >= ago(dt_lookBack)
| extend EmailUrlInfo_TimeGenerated = TimeGenerated);
let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url);
let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain);
let EmailEvents_ = materialize(EmailEvents
| where TimeGenerated >= ago(dt_lookBack));
let TI = materialize(ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend Url = ObservableValue
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (EmailUrls) or tolower(Url) in (EmailUrlDomains)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil)));
(union
(TI | join kind=innerunique (EmailUrlInfo_) on Url),
(TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain))
| where EmailUrlInfo_TimeGenerated < ValidUntil
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId
| extend timestamp = EmailUrlInfo_TimeGenerated
| join kind=inner (EmailEvents_) on NetworkMessageId
| where DeliveryAction !has "Blocked"
| extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0])
name: TI Map URL Entity to EmailUrlInfo
entityMappings:
- fieldMappings:
- columnName: RecipientEmailAddress
identifier: FullName
- columnName: Name
identifier: Name
- columnName: UPNSuffix
identifier: UPNSuffix
entityType: Account
- fieldMappings:
- columnName: Url
identifier: Url
entityType: URL
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Threat Intelligence (NEW)/Analytic Rules/URLEntity_EmailUrlInfo_Updated.yaml
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EmailUrlInfo
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
description: |
'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo.'
kind: Scheduled
version: 1.0.4
queryFrequency: 1h
severity: Medium
relevantTechniques:
- T1071
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
id: 9e32e545-e60c-47de-9941-f9ca1ada0a42