Detection rules › Kusto

GSA - Detect Protocol Changes for Destination Ports

Status
available
Severity
medium
Time window
8d
Group by
AlertTimeDstPort, AlertTimeProtocol, DestinationFqdn, SourceIp
Source
github.com/Azure/Azure-Sentinel

Identifies changes in the protocol used for specific destination ports, comparing the current runtime with a learned baseline. This can indicate potential protocol misuse or configuration changes. Configurable Parameters: - Learning period: The time range to establish the baseline. Default is set to 7 days. - Run time: The time range for current analysis. Default is set to 1 day.

MITRE ATT&CK coverage

TacticTechniques
Command & ControlT1571 Non-Standard Port

Rule body kusto

id: f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a
name: GSA - Detect Protocol Changes for Destination Ports
description: |
  Identifies changes in the protocol used for specific destination ports, comparing the current runtime with a learned baseline.
  This can indicate potential protocol misuse or configuration changes.
  Configurable Parameters:
  - Learning period: The time range to establish the baseline. Default is set to 7 days.
  - Run time: The time range for current analysis. Default is set to 1 day.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - NetworkAccessTrafficLogs
queryFrequency: 1h
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 1
tactics:
  - DefenseEvasion
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1571
query: |
  let LearningPeriod = 7d;
  let RunTime = 1d;
  let StartLearningPeriod = ago(LearningPeriod + RunTime);
  let EndRunTime = ago(RunTime);
  let LearningPortToProtocol = 
    NetworkAccessTraffic
    | where TimeGenerated between (StartLearningPeriod .. EndRunTime)
    | where isnotempty(DestinationPort)
    | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;
  let AlertTimePortToProtocol = 
    NetworkAccessTraffic
    | where TimeGenerated between (EndRunTime .. now())
    | where isnotempty(DestinationPort)
    | summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;
  AlertTimePortToProtocol
    | join kind=leftouter (LearningPortToProtocol) on $left.AlertTimeDstPort == $right.LearningTimeDstPort and $left.SourceIp == $right.SourceIp and $left.DestinationFqdn == $right.DestinationFqdn
    | where isnotempty(LearningTimeProtocol) and isnotempty(AlertTimeProtocol) and LearningTimeProtocol != AlertTimeProtocol
    | project AlertTimeDstPort, AlertTimeProtocol, LearningTimeProtocol, SourceIp, DestinationFqdn
    | extend IPCustomEntity = SourceIp, FqdnCustomEntity = DestinationFqdn
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: FqdnCustomEntity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let LearningPeriod = 7d;
let RunTime = 1d;
let StartLearningPeriod = ago(LearningPeriod + RunTime);
let EndRunTime = ago(RunTime);

Let binding: LearningPortToProtocol

let LearningPortToProtocol = NetworkAccessTraffic
  | where TimeGenerated between (StartLearningPeriod .. EndRunTime)
  | where isnotempty(DestinationPort)
  | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;

Derived from StartLearningPeriod, EndRunTime.

The stages below define let AlertTimePortToProtocol (the rule's main pipeline source).

Stage 1: source

NetworkAccessTraffic

Stage 2: where

| where TimeGenerated between (EndRunTime .. now())

Stage 3: where

| where isnotempty(DestinationPort)

Stage 4: summarize

| summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn

The stages below run on AlertTimePortToProtocol (the outer pipeline).

Stage 5: join

AlertTimePortToProtocol
| join kind=leftouter (LearningPortToProtocol) on $left.AlertTimeDstPort == $right.LearningTimeDstPort and $left.SourceIp == $right.SourceIp and $left.DestinationFqdn == $right.DestinationFqdn

Stage 6: where

| where isnotempty(LearningTimeProtocol) and isnotempty(AlertTimeProtocol) and LearningTimeProtocol != AlertTimeProtocol

Stage 7: project

| project AlertTimeDstPort, AlertTimeProtocol, LearningTimeProtocol, SourceIp, DestinationFqdn

Stage 8: extend

| extend IPCustomEntity = SourceIp, FqdnCustomEntity = DestinationFqdn

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
AlertTimeProtocolis_not_null
  • (no value, null check)
DestinationPortis_not_null
  • (no value, null check)
LearningTimeProtocolis_not_null
  • (no value, null check)
LearningTimeProtocolne
  • AlertTimeProtocol transforms: cased
TimeGeneratedge
  • StartLearningPeriod
TimeGeneratedle
  • EndRunTime

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
AlertTimeDstPortproject
AlertTimeProtocolproject
DestinationFqdnproject
LearningTimeProtocolproject
SourceIpproject
FqdnCustomEntityextend
IPCustomEntityextend