Detection rules › Kusto

Port Sweep

Status
available
Severity
medium
Time window
30s
Group by
DestinationPort, SourceIp
Source
github.com/Azure/Azure-Sentinel

'Identifies a source IP scanning same open ports on the Azure Firewall IPs. This can indicate malicious scanning of port by an attacker, trying to reveal IPs with specific ports open in the organization. The ports can be compromised by attackers for initial access, most often by exploiting vulnerability. Configurable Parameters: - Port sweep time - the time range to look for multiple hosts scanned. Default is set to 30 seconds. - Minimum different hosts threshold - alert only if more than this number of hosts scanned. Default is set to 200.'

MITRE ATT&CK coverage

Rule body

id: 720335f4-ee8c-4270-9424-d0859222168c
name: Port Sweep
description: |
  'Identifies a source IP scanning same open ports on the Azure Firewall IPs. This can indicate malicious scanning of port by an attacker, trying to reveal IPs with specific ports open in the organization. The ports can be compromised by attackers for initial access, most often by exploiting vulnerability.
  
  Configurable Parameters:
  
  - Port sweep time - the time range to look for multiple hosts scanned. Default is set to 30 seconds.
  - Minimum different hosts threshold - alert only if more than this number of hosts scanned. Default is set to 200.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
      - AZFWApplicationRule
      - AZFWNetworkRule
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - Reconnaissance
relevantTechniques:
  - T1046
  - T1595.001
query: |
  let MinimumDifferentHostsThreshold = 200;
  let ExcludedPorts = dynamic([80 , 443]);
  let BinTime = 30s;
  union isfuzzy=true(
  AZFWApplicationRule
  | where DestinationPort !in (ExcludedPorts)
  | summarize AlertTimedCountHostsInBinTime = make_set(Fqdn) by SourceIp, bin(TimeGenerated, BinTime), DestinationPort
  | where array_length(AlertTimedCountHostsInBinTime) > MinimumDifferentHostsThreshold
  | mv-expand Fqdn = AlertTimedCountHostsInBinTime),
  (AZFWNetworkRule
  | extend Fqdn = DestinationIp
  | where DestinationPort !in (ExcludedPorts)
  | summarize AlertTimedCountHostsInBinTime = make_set(Fqdn) by SourceIp, bin(TimeGenerated, BinTime), DestinationPort
  | where array_length(AlertTimedCountHostsInBinTime) > MinimumDifferentHostsThreshold
  | mv-expand Fqdn = AlertTimedCountHostsInBinTime),
  (AzureDiagnostics
  | where OperationName == "AzureFirewallApplicationRuleLog" or OperationName == "AzureFirewallNetworkRuleLog"
  | parse msg_s with * "from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int ". " * "Action: " Action "." *
  | where DestinationPort !in (ExcludedPorts)
  | where isnotempty(Fqdn) and isnotempty(SourceIp) and isnotempty(DestinationPort)
  | summarize AlertTimedCountHostsInBinTime = make_set(Fqdn) by SourceIp, bin(TimeGenerated, BinTime), DestinationPort
  | where array_length(AlertTimedCountHostsInBinTime) > MinimumDifferentHostsThreshold
  | mv-expand Fqdn = AlertTimedCountHostsInBinTime)
  | project bin(TimeGenerated, BinTime), SourceIp, DestinationPort, AlertTimedCountHostsInBinTime, Fqdn
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Fqdn
customDetails:
  DestinationPort: DestinationPort
alertDetailsOverride:
  alertDisplayNameFormat: 'Port Sweep Detected from {{SourceIp}} on Port {{DestinationPort}}'
  alertDescriptionFormat: 'Source IP {{SourceIp}} has scanned {{AlertTimedCountHostsInBinTime}} different hosts on port {{DestinationPort}} within a short time frame, which may indicate reconnaissance activity.'
version: 1.2.3
kind: Scheduled

Stages and Predicates

Stage 0: let

let MinimumDifferentHostsThreshold = 200;
let ExcludedPorts = dynamic([80 , 443]);
let BinTime = 30s;

Stage 1: union

union of 3 branches

Stage 2: source

AZFWApplicationRule

Stage 3: where

| where DestinationPort !in (ExcludedPorts)

Stage 4: summarize

| summarize AlertTimedCountHostsInBinTime = make_set(Fqdn) by SourceIp, bin(TimeGenerated, BinTime), DestinationPort

Stage 5: where

| where array_length(AlertTimedCountHostsInBinTime) > MinimumDifferentHostsThreshold

Stage 6: mv-expand

| mv-expand Fqdn = AlertTimedCountHostsInBinTime

Stage 7: source

AZFWNetworkRule

Stage 8: extend

| extend Fqdn = DestinationIp

Stage 9: where

| where DestinationPort !in (ExcludedPorts)

Stage 10: summarize

| summarize AlertTimedCountHostsInBinTime = make_set(Fqdn) by SourceIp, bin(TimeGenerated, BinTime), DestinationPort

Stage 11: where

| where array_length(AlertTimedCountHostsInBinTime) > MinimumDifferentHostsThreshold

Stage 12: mv-expand

| mv-expand Fqdn = AlertTimedCountHostsInBinTime

Stage 13: source

AzureDiagnostics

Stage 14: where

| where OperationName == "AzureFirewallApplicationRuleLog" or OperationName == "AzureFirewallNetworkRuleLog"

Stage 15: parse

| parse msg_s with * "from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int ". " * "Action: " Action "." *

Stage 16: where

| where DestinationPort !in (ExcludedPorts)

Stage 17: where

| where isnotempty(Fqdn) and isnotempty(SourceIp) and isnotempty(DestinationPort)

Stage 18: summarize

| summarize AlertTimedCountHostsInBinTime = make_set(Fqdn) by SourceIp, bin(TimeGenerated, BinTime), DestinationPort

Stage 19: where

| where array_length(AlertTimedCountHostsInBinTime) > MinimumDifferentHostsThreshold

Stage 20: mv-expand

| mv-expand Fqdn = AlertTimedCountHostsInBinTime

Stage 21: project

project AlertTimedCountHostsInBinTime, DestinationPort, Fqdn, SourceIp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DestinationPortis_not_null
  • (no value, null check)
field:"DestinationPort" kind:is_not_null
Fqdnis_not_null
  • (no value, null check)
field:"Fqdn" kind:is_not_null
OperationNameeq
  • AzureFirewallApplicationRuleLog
  • AzureFirewallNetworkRuleLog
field:"OperationName" kind:eq
SourceIpis_not_null
  • (no value, null check)
field:"src_ip" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

Output fields

These fields are emitted when the rule matches.

FieldSource
AlertTimedCountHostsInBinTimeproject
DestinationPortproject
Fqdnproject
SourceIpproject