Detection rules › Kusto
CloudNGFW By Palo Alto Networks - Threat signatures from Unusual IP addresses
'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen. This detection is also leveraged and required for MDE and PAN Fusion scenario https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Command & Control | |
| Exfiltration |
Rule body
id: 89a86f70-615f-4a79-9621-6f68c50f365f
name: CloudNGFW By Palo Alto Networks - Threat signatures from Unusual IP addresses
description: |
'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen.
This detection is also leveraged and required for MDE and PAN Fusion scenario
https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureCloudNGFWByPaloAltoNetworks
dataTypes:
- fluentbit_CL
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1046
- T1030
- T1071.001
tags:
- Fusion
query: |
let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;
let HistoricalThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
let CurrentHourThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated > ago(timeframe)
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
CurrentHourThreats
| where TotalEvents < CurrThreshold
| join kind = leftanti (HistoricalThreats
| where TotalEvents > HistThreshold) on src_ip
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: src_ip
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 0: let
let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;
let HistoricalThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
let CurrentHourThreats = fluentbit_CL <inlined as stages below>;
Stage 1: source
fluentbit_CL
Stage 2: where
| where ident_s == "THREAT"
Stage 3: extend
| extend message = parse_json(Message)
Stage 4: where
| where isnotempty(message.src_ip)
Stage 5: where
| where TimeGenerated > ago(timeframe)
Stage 6: where
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
Stage 7: extend
| extend src_ip = tostring(message.src_ip)
Stage 8: summarize
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s
Stage 9: where
| where TotalEvents < CurrThreshold
Stage 10: join (negated)
join kind=leftanti (HistoricalThreats) on src_ip
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
TotalEvents | lt |
| field:"TotalEvents" kind:lt value:"10" |
ident_s | eq |
| field:"ident_s" kind:eq value:"THREAT" |
src_ip | is_not_null | field:"src_ip" kind:is_not_null | |
sub_type | in |
| field:"sub_type" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TotalEvents | gt | 25 | excludes:TotalEvents field:"TotalEvents" value:"25" |
ident_s | eq | THREAT | excludes:ident_s field:"ident_s" value:"THREAT" |
src_ip | is_not_null | excludes:src_ip | |
sub_type | in | file, flood, packet, scan, spyware, virus, vulnerability, wildfire, wildfire-virus | excludes:sub_type |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationIpList | summarize |
FirewallName_s | summarize |
FirstSeen | summarize |
LastSeen | summarize |
ThreatTypes | summarize |
TotalEvents | summarize |
src_ip | summarize |