Analytic rule catalog
GreyNoise TI Map IP Entity to CommonSecurityLog
Back
| Id | e50657d7-8bca-43ff-a647-d407fae440d6 |
| Rulename | GreyNoise TI Map IP Entity to CommonSecurityLog |
| Description | This query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog. |
| Severity | Medium |
| Tactics | CommandAndControl |
| Techniques | T1071 |
| Required data connectors | CEF CefAma GreyNoise2SentinelAPI ThreatIntelligence |
| Kind | Scheduled |
| Query frequency | 4h |
| Query period | 14d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/GreyNoiseThreatIntelligence/Analytic%20Rules/GreyNoise_IPEntity_CustomSecurityLog.yaml |
| Version | 1.0.3 |
| Arm template | e50657d7-8bca-43ff-a647-d407fae440d6.json |
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"
}
]
}