Back
Id3b6bdb38-93c5-452f-ab3a-97a3d1320d16
RulenameTI Map URL Entity to SecurityAlert Data
DescriptionThis query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.
SeverityMedium
TacticsCommandAndControl
TechniquesT1071
Required data connectorsAzureSecurityCenter
MicrosoftCloudAppSecurity
MicrosoftDefenderThreatIntelligence
ThreatIntelligence
ThreatIntelligenceTaxii
KindScheduled
Query frequency1h
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Threat%20Intelligence%20%28NEW%29/Analytic%20Rules/URLEntity_SecurityAlerts.yaml
Version1.2.11
Arm template3b6bdb38-93c5-452f-ab3a-97a3d1320d16.json
Deploy To Azure
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let URLRegex = "((https?|ftp|ldap|wss?|file):\\/\\/(([\\:\\%\\w\\_\\-]+(\\.|@))*((xn--)?[a-zA-Z0-9\\-]+\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{0,3})[.,:\\w@?^=%&\\/~+#-]*[\\w@?^=%&\\/~+#-])";
let SecurityEvents = materialize( SecurityAlert
  | where TimeGenerated >= ago(dt_lookBack)
  | extend MSTI = case(AlertName has "TI map" and VendorName == "Microsoft" and ProductName == 'Azure Sentinel', true, false)
  | where MSTI == false
  | mv-expand Entity = parse_json(Entities)
  | extend RawUrl = coalesce(Entity.Url, Entity.Urls)
  | mv-expand RawUrl
  | extend Url = tostring(RawUrl)
  | extend Compromised_Host = tostring(parse_json(ExtendedProperties).["Compromised Host"])
  | extend Alert_TimeGenerated = TimeGenerated);
let EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));
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)
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (EventUrls)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend Description = tostring(parse_json(Data).description)
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;" 
 | project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (SecurityEvents) on Url
| where Alert_TimeGenerated < ValidUntil
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by Id, AlertName
| project timestamp = Alert_TimeGenerated, ActivityGroupNames, Id, Type, ValidUntil, Confidence, AlertName, AlertSeverity, Description, Url, Compromised_Host
tactics:
- CommandAndControl
triggerThreshold: 0
relevantTechniques:
- T1071
queryPeriod: 14d
name: TI Map URL Entity to SecurityAlert Data
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.'
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Threat%20Intelligence%20%28NEW%29/Analytic%20Rules/URLEntity_SecurityAlerts.yaml
kind: Scheduled
entityMappings:
- fieldMappings:
  - columnName: Compromised_Host
    identifier: HostName
  entityType: Host
- fieldMappings:
  - columnName: Url
    identifier: Url
  entityType: URL
triggerOperator: gt
id: 3b6bdb38-93c5-452f-ab3a-97a3d1320d16
severity: Medium
version: 1.2.11
requiredDataConnectors:
- connectorId: MicrosoftCloudAppSecurity
  dataTypes:
  - SecurityAlert
- connectorId: AzureSecurityCenter
  dataTypes:
  - SecurityAlert
- connectorId: ThreatIntelligence
  dataTypes:
  - ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
  dataTypes:
  - ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
  dataTypes:
  - ThreatIntelIndicators
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let URLRegex = "((https?|ftp|ldap|wss?|file):\\/\\/(([\\:\\%\\w\\_\\-]+(\\.|@))*((xn--)?[a-zA-Z0-9\\-]+\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{0,3})[.,:\\w@?^=%&\\/~+#-]*[\\w@?^=%&\\/~+#-])";
  let SecurityEvents = materialize( SecurityAlert
    | where TimeGenerated >= ago(dt_lookBack)
    | extend MSTI = case(AlertName has "TI map" and VendorName == "Microsoft" and ProductName == 'Azure Sentinel', true, false)
    | where MSTI == false
    | mv-expand Entity = parse_json(Entities)
    | extend RawUrl = coalesce(Entity.Url, Entity.Urls)
    | mv-expand RawUrl
    | extend Url = tostring(RawUrl)
    | extend Compromised_Host = tostring(parse_json(ExtendedProperties).["Compromised Host"])
    | extend Alert_TimeGenerated = TimeGenerated);
  let EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));
  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)
  | where TimeGenerated >= ago(ioc_lookBack)
  | where tolower(Url) in (EventUrls)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
  | extend Description = tostring(parse_json(Data).description)
  | where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;" 
   | project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (SecurityEvents) on Url
  | where Alert_TimeGenerated < ValidUntil
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by Id, AlertName
  | project timestamp = Alert_TimeGenerated, ActivityGroupNames, Id, Type, ValidUntil, Confidence, AlertName, AlertSeverity, Description, Url, Compromised_Host
queryFrequency: 1h
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2024-01-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/3b6bdb38-93c5-452f-ab3a-97a3d1320d16')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/3b6bdb38-93c5-452f-ab3a-97a3d1320d16')]",
      "properties": {
        "alertRuleTemplateName": "3b6bdb38-93c5-452f-ab3a-97a3d1320d16",
        "customDetails": null,
        "description": "'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.'\n",
        "displayName": "TI Map URL Entity to SecurityAlert Data",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Host",
            "fieldMappings": [
              {
                "columnName": "Compromised_Host",
                "identifier": "HostName"
              }
            ]
          },
          {
            "entityType": "URL",
            "fieldMappings": [
              {
                "columnName": "Url",
                "identifier": "Url"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Threat%20Intelligence%20%28NEW%29/Analytic%20Rules/URLEntity_SecurityAlerts.yaml",
        "query": "let dt_lookBack = 1h;\nlet ioc_lookBack = 14d;\nlet URLRegex = \"((https?|ftp|ldap|wss?|file):\\\\/\\\\/(([\\\\:\\\\%\\\\w\\\\_\\\\-]+(\\\\.|@))*((xn--)?[a-zA-Z0-9\\\\-]+\\\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{0,3})[.,:\\\\w@?^=%&\\\\/~+#-]*[\\\\w@?^=%&\\\\/~+#-])\";\nlet SecurityEvents = materialize( SecurityAlert\n  | where TimeGenerated >= ago(dt_lookBack)\n  | extend MSTI = case(AlertName has \"TI map\" and VendorName == \"Microsoft\" and ProductName == 'Azure Sentinel', true, false)\n  | where MSTI == false\n  | mv-expand Entity = parse_json(Entities)\n  | extend RawUrl = coalesce(Entity.Url, Entity.Urls)\n  | mv-expand RawUrl\n  | extend Url = tostring(RawUrl)\n  | extend Compromised_Host = tostring(parse_json(ExtendedProperties).[\"Compromised Host\"])\n  | extend Alert_TimeGenerated = TimeGenerated);\nlet EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));\nThreatIntelIndicators\n//extract key part of kv pair\n| extend IndicatorType = replace(@\"\\[|\\]|\\\"\"\", \"\", tostring(split(ObservableKey, \":\", 0)))\n| where IndicatorType == \"url\"\n| extend Url = ObservableValue\n| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)\n| where TimeGenerated >= ago(ioc_lookBack)\n| where tolower(Url) in (EventUrls)\n| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue\n| where IsActive and (ValidUntil > now() or isempty(ValidUntil))\n| extend Description = tostring(parse_json(Data).description)\n| where Description !contains_cs \"State: inactive;\" and Description !contains_cs \"State: falsepos;\" \n | project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type\n// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated\n| join kind=innerunique (SecurityEvents) on Url\n| where Alert_TimeGenerated < ValidUntil\n| extend Description = tostring(parse_json(Data).description)\n| extend ActivityGroupNames = extract(@\"ActivityGroup:(\\S+)\", 1, tostring(parse_json(Data).labels))\n| summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by Id, AlertName\n| project timestamp = Alert_TimeGenerated, ActivityGroupNames, Id, Type, ValidUntil, Confidence, AlertName, AlertSeverity, Description, Url, Compromised_Host\n",
        "queryFrequency": "PT1H",
        "queryPeriod": "P14D",
        "severity": "Medium",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "CommandAndControl"
        ],
        "techniques": [
          "T1071"
        ],
        "templateVersion": "1.2.11",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}