Back
Idddf47b6f-870c-5712-a296-1383acb13c82
RulenameGreyNoise TI Map IP Entity to DnsEvents
DescriptionThis query maps any IP indicators of compromise (IOCs) from GreyNoise Threat Intelligence (TI), by searching for matches in DnsEvents.
SeverityMedium
TacticsCommandAndControl
TechniquesT1071
Required data connectorsASimDnsActivityLogs
DNS
GreyNoise2SentinelAPI
ThreatIntelligence
ThreatIntelligenceTaxii
KindScheduled
Query frequency1h
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_DnsEvents.yaml
Version1.0.3
Arm templateddf47b6f-870c-5712-a296-1383acb13c82.json
Deploy To Azure
let dt_lookBack = 1h; // Look back 1 hour for DNS events
let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
// Fetch GreyNoise IPv4 indicators from the ThreatIntelIndicators (STIX 2.1) table.
// GreyNoise only publishes ipv4-addr patterns, so a single ObservableKey filter is sufficient.
let GreyNoise_IPs = ThreatIntelIndicators
  | where TimeGenerated >= ago(ioc_lookBack)
  | where SourceSystem == 'GreyNoise'
  | where ObservableKey == 'ipv4-addr:value'
  // Take the latest row per indicator first, then evaluate its current state, so an
  // indicator that has since been deactivated cannot be resurrected by an older row.
  | summarize arg_max(TimeGenerated, *) by Id
  | where IsActive == true and IsDeleted == false and ValidUntil > now()
  | extend TI_ipEntity = ObservableValue
  | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
  // Explicit projection keeps TimeGenerated and Type off the left side of the join, avoiding column collisions.
  | project IndicatorId = Id, TI_ipEntity, ThreatType = tostring(Data.indicator_types[0]), IoCDescription = tostring(Data.description), Tags, Confidence, ValidFrom, ValidUntil;
// Perform a join between IP indicators and DNS events
GreyNoise_IPs
  // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
  | join kind=innerunique (
      DnsEvents
      | where TimeGenerated >= ago(dt_lookBack)
      | where SubType =~ "LookupQuery" and isnotempty(IPAddresses)
      | mv-expand SingleIP = split(IPAddresses, ", ") to typeof(string)
      | extend DNS_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.SingleIP
  // Filter out DNS events that occurred after the expiration of the corresponding indicator
  | where DNS_TimeGenerated < ValidUntil
  // Group the results by IndicatorId and SingleIP, and keep the DNS event with the latest timestamp
  | summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by IndicatorId, SingleIP
  // Select the desired output fields
  | project DNS_TimeGenerated, IoCDescription, IndicatorId, ThreatType, ValidFrom, ValidUntil, Confidence, Tags,
    TI_ipEntity, Computer, EventId, SubType, ClientIP, Name, IPAddresses, Type
  | extend timestamp = DNS_TimeGenerated, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
name: GreyNoise TI Map IP Entity to DnsEvents
triggerOperator: gt
kind: Scheduled
queryFrequency: 1h
description: |
  This query maps any IP indicators of compromise (IOCs) from GreyNoise Threat Intelligence (TI), by searching for matches in DnsEvents.
id: ddf47b6f-870c-5712-a296-1383acb13c82
triggerThreshold: 0
queryPeriod: 14d
query: |
  let dt_lookBack = 1h; // Look back 1 hour for DNS events
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  // Fetch GreyNoise IPv4 indicators from the ThreatIntelIndicators (STIX 2.1) table.
  // GreyNoise only publishes ipv4-addr patterns, so a single ObservableKey filter is sufficient.
  let GreyNoise_IPs = ThreatIntelIndicators
    | where TimeGenerated >= ago(ioc_lookBack)
    | where SourceSystem == 'GreyNoise'
    | where ObservableKey == 'ipv4-addr:value'
    // Take the latest row per indicator first, then evaluate its current state, so an
    // indicator that has since been deactivated cannot be resurrected by an older row.
    | summarize arg_max(TimeGenerated, *) by Id
    | where IsActive == true and IsDeleted == false and ValidUntil > now()
    | extend TI_ipEntity = ObservableValue
    | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
    // Explicit projection keeps TimeGenerated and Type off the left side of the join, avoiding column collisions.
    | project IndicatorId = Id, TI_ipEntity, ThreatType = tostring(Data.indicator_types[0]), IoCDescription = tostring(Data.description), Tags, Confidence, ValidFrom, ValidUntil;
  // Perform a join between IP indicators and DNS events
  GreyNoise_IPs
    // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
    | join kind=innerunique (
        DnsEvents
        | where TimeGenerated >= ago(dt_lookBack)
        | where SubType =~ "LookupQuery" and isnotempty(IPAddresses)
        | mv-expand SingleIP = split(IPAddresses, ", ") to typeof(string)
        | extend DNS_TimeGenerated = TimeGenerated
    )
    on $left.TI_ipEntity == $right.SingleIP
    // Filter out DNS events that occurred after the expiration of the corresponding indicator
    | where DNS_TimeGenerated < ValidUntil
    // Group the results by IndicatorId and SingleIP, and keep the DNS event with the latest timestamp
    | summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by IndicatorId, SingleIP
    // Select the desired output fields
    | project DNS_TimeGenerated, IoCDescription, IndicatorId, ThreatType, ValidFrom, ValidUntil, Confidence, Tags,
      TI_ipEntity, Computer, EventId, SubType, ClientIP, Name, IPAddresses, Type
    | extend timestamp = DNS_TimeGenerated, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
version: 1.0.3
requiredDataConnectors:
- connectorId: ThreatIntelligence
  dataTypes:
  - ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
  dataTypes:
  - ThreatIntelIndicators
- connectorId: DNS
  dataTypes:
  - DnsEvents
- connectorId: ASimDnsActivityLogs
  dataTypes:
  - DnsEvents
- connectorId: GreyNoise2SentinelAPI
  dataTypes:
  - ThreatIntelIndicators
severity: Medium
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_DnsEvents.yaml
relevantTechniques:
- T1071
tactics:
- CommandAndControl
entityMappings:
- fieldMappings:
  - identifier: HostName
    columnName: HostName
  - identifier: DnsDomain
    columnName: DnsDomain
  entityType: Host
- fieldMappings:
  - identifier: Address
    columnName: ClientIP
  entityType: IP
{
  "$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/ddf47b6f-870c-5712-a296-1383acb13c82')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/ddf47b6f-870c-5712-a296-1383acb13c82')]",
      "properties": {
        "alertRuleTemplateName": "ddf47b6f-870c-5712-a296-1383acb13c82",
        "customDetails": null,
        "description": "This query maps any IP indicators of compromise (IOCs) from GreyNoise Threat Intelligence (TI), by searching for matches in DnsEvents.\n",
        "displayName": "GreyNoise TI Map IP Entity to DnsEvents",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Host",
            "fieldMappings": [
              {
                "columnName": "HostName",
                "identifier": "HostName"
              },
              {
                "columnName": "DnsDomain",
                "identifier": "DnsDomain"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "ClientIP",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_DnsEvents.yaml",
        "query": "let dt_lookBack = 1h; // Look back 1 hour for DNS events\nlet ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators\n// Fetch GreyNoise IPv4 indicators from the ThreatIntelIndicators (STIX 2.1) table.\n// GreyNoise only publishes ipv4-addr patterns, so a single ObservableKey filter is sufficient.\nlet GreyNoise_IPs = ThreatIntelIndicators\n  | where TimeGenerated >= ago(ioc_lookBack)\n  | where SourceSystem == 'GreyNoise'\n  | where ObservableKey == 'ipv4-addr:value'\n  // Take the latest row per indicator first, then evaluate its current state, so an\n  // indicator that has since been deactivated cannot be resurrected by an older row.\n  | summarize arg_max(TimeGenerated, *) by Id\n  | where IsActive == true and IsDeleted == false and ValidUntil > now()\n  | extend TI_ipEntity = ObservableValue\n  | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith \"fe80\" and TI_ipEntity !startswith \"::\" and TI_ipEntity !startswith \"127.\"\n  // Explicit projection keeps TimeGenerated and Type off the left side of the join, avoiding column collisions.\n  | project IndicatorId = Id, TI_ipEntity, ThreatType = tostring(Data.indicator_types[0]), IoCDescription = tostring(Data.description), Tags, Confidence, ValidFrom, ValidUntil;\n// Perform a join between IP indicators and DNS events\nGreyNoise_IPs\n  // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation\n  | join kind=innerunique (\n      DnsEvents\n      | where TimeGenerated >= ago(dt_lookBack)\n      | where SubType =~ \"LookupQuery\" and isnotempty(IPAddresses)\n      | mv-expand SingleIP = split(IPAddresses, \", \") to typeof(string)\n      | extend DNS_TimeGenerated = TimeGenerated\n  )\n  on $left.TI_ipEntity == $right.SingleIP\n  // Filter out DNS events that occurred after the expiration of the corresponding indicator\n  | where DNS_TimeGenerated < ValidUntil\n  // Group the results by IndicatorId and SingleIP, and keep the DNS event with the latest timestamp\n  | summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by IndicatorId, SingleIP\n  // Select the desired output fields\n  | project DNS_TimeGenerated, IoCDescription, IndicatorId, ThreatType, ValidFrom, ValidUntil, Confidence, Tags,\n    TI_ipEntity, Computer, EventId, SubType, ClientIP, Name, IPAddresses, Type\n  | extend timestamp = DNS_TimeGenerated, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))\n",
        "queryFrequency": "PT1H",
        "queryPeriod": "P14D",
        "severity": "Medium",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "CommandAndControl"
        ],
        "techniques": [
          "T1071"
        ],
        "templateVersion": "1.0.3",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}