Back
Ide50657d7-8bca-43ff-a647-d407fae440d6
RulenameGreyNoise TI Map IP Entity to CommonSecurityLog
DescriptionThis query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.
SeverityMedium
TacticsCommandAndControl
TechniquesT1071
Required data connectorsCEF
CefAma
GreyNoise2SentinelAPI
ThreatIntelligence
KindScheduled
Query frequency4h
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_CustomSecurityLog.yaml
Version1.0.3
Arm templatee50657d7-8bca-43ff-a647-d407fae440d6.json
Deploy To Azure
let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
let dt_lookBack = 1h; // Look back 1 hour for CommonSecurityLog 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 CommonSecurityLog 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 (
      CommonSecurityLog
      | where TimeGenerated >= ago(dt_lookBack)
      | extend MessageIP = extract(IPRegex, 0, Message)
      | extend CS_ipEntity = iff(isnotempty(SourceIP), SourceIP, DestinationIP)
      | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)
      | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.CS_ipEntity
  // Filter out logs that occurred after the expiration of the corresponding indicator
  | where CommonSecurityLog_TimeGenerated < ValidUntil
  // Group the results by IndicatorId and CS_ipEntity, and keep the log entry with the latest timestamp
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, CS_ipEntity
  // Select the desired output fields
  | project timestamp = CommonSecurityLog_TimeGenerated, SourceIP, DestinationIP, MessageIP, Message, DeviceVendor, DeviceProduct, IndicatorId, ThreatType, IoCDescription, ValidFrom, ValidUntil, Confidence, Tags, TI_ipEntity, CS_ipEntity, LogSeverity, DeviceAction, Type
name: GreyNoise TI Map IP Entity to CommonSecurityLog
triggerOperator: gt
query: |
  let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
  let dt_lookBack = 1h; // Look back 1 hour for CommonSecurityLog 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 CommonSecurityLog 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 (
        CommonSecurityLog
        | where TimeGenerated >= ago(dt_lookBack)
        | extend MessageIP = extract(IPRegex, 0, Message)
        | extend CS_ipEntity = iff(isnotempty(SourceIP), SourceIP, DestinationIP)
        | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)
        | extend CommonSecurityLog_TimeGenerated = TimeGenerated
    )
    on $left.TI_ipEntity == $right.CS_ipEntity
    // Filter out logs that occurred after the expiration of the corresponding indicator
    | where CommonSecurityLog_TimeGenerated < ValidUntil
    // Group the results by IndicatorId and CS_ipEntity, and keep the log entry with the latest timestamp
    | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, CS_ipEntity
    // Select the desired output fields
    | project timestamp = CommonSecurityLog_TimeGenerated, SourceIP, DestinationIP, MessageIP, Message, DeviceVendor, DeviceProduct, IndicatorId, ThreatType, IoCDescription, ValidFrom, ValidUntil, Confidence, Tags, TI_ipEntity, CS_ipEntity, LogSeverity, DeviceAction, Type
queryFrequency: 4h
description: |
  This query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.
id: e50657d7-8bca-43ff-a647-d407fae440d6
triggerThreshold: 0
queryPeriod: 14d
version: 1.0.3
kind: Scheduled
relevantTechniques:
- T1071
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
  dataTypes:
  - ThreatIntelIndicators
- connectorId: CEF
  dataTypes:
  - CommonSecurityLog
- connectorId: CefAma
  dataTypes:
  - CommonSecurityLog
- connectorId: GreyNoise2SentinelAPI
  dataTypes:
  - ThreatIntelIndicators
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_CustomSecurityLog.yaml
tactics:
- CommandAndControl
entityMappings:
- fieldMappings:
  - identifier: Address
    columnName: CS_ipEntity
  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/e50657d7-8bca-43ff-a647-d407fae440d6')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/e50657d7-8bca-43ff-a647-d407fae440d6')]",
      "properties": {
        "alertRuleTemplateName": "e50657d7-8bca-43ff-a647-d407fae440d6",
        "customDetails": null,
        "description": "This query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.\n",
        "displayName": "GreyNoise TI Map IP Entity to CommonSecurityLog",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "CS_ipEntity",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_CustomSecurityLog.yaml",
        "query": "let IPRegex = '[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}';\nlet dt_lookBack = 1h; // Look back 1 hour for CommonSecurityLog 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 CommonSecurityLog 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      CommonSecurityLog\n      | where TimeGenerated >= ago(dt_lookBack)\n      | extend MessageIP = extract(IPRegex, 0, Message)\n      | extend CS_ipEntity = iff(isnotempty(SourceIP), SourceIP, DestinationIP)\n      | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)\n      | extend CommonSecurityLog_TimeGenerated = TimeGenerated\n  )\n  on $left.TI_ipEntity == $right.CS_ipEntity\n  // Filter out logs that occurred after the expiration of the corresponding indicator\n  | where CommonSecurityLog_TimeGenerated < ValidUntil\n  // Group the results by IndicatorId and CS_ipEntity, and keep the log entry with the latest timestamp\n  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, CS_ipEntity\n  // Select the desired output fields\n  | project timestamp = CommonSecurityLog_TimeGenerated, SourceIP, DestinationIP, MessageIP, Message, DeviceVendor, DeviceProduct, IndicatorId, ThreatType, IoCDescription, ValidFrom, ValidUntil, Confidence, Tags, TI_ipEntity, CS_ipEntity, LogSeverity, DeviceAction, Type\n",
        "queryFrequency": "PT4H",
        "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"
    }
  ]
}