Detection rules › Kusto

Abnormal Port to Protocol

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

'Identifies communication for well known protocol over a non-standard port based on learning period activity. This can indicate malicious communication (C2) or exfiltration by attackers trying to communicate over known ports (22:SSH, 80:HTTP) but dont use the known protocol headers to match the port number. Configurable Parameters: - Learning period time - learning period for protocol learning in days. Default is set to 7.'

MITRE ATT&CK coverage

Rule body

id: 826f930c-2f25-4508-8e75-a95b809a4e15
name: Abnormal Port to Protocol
description: |
  'Identifies communication for well known protocol over a non-standard port based on learning period activity. This can indicate malicious communication (C2) or exfiltration by attackers trying to communicate over known ports (22:SSH, 80:HTTP) but dont use the known protocol headers to match the port number.
  
  Configurable Parameters:
  
  - Learning period time - learning period for protocol learning in days. Default is set to 7.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
      - AZFWApplicationRule
      - AZFWNetworkRule
queryFrequency: 1h
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
  - DefenseEvasion
relevantTechniques:
  - T1041
  - T1571
  - T1572
query: |
  let LearningPeriod = 7d;
  let RunTime = 1h;
  let StartLearningPeriod = LearningPeriod + RunTime;
  let DetectionWindowStart = ago(RunTime);
  let LearningPortToProtocol1 =  (AzureDiagnostics
  | where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
  | where OperationName == "AzureFirewallApplicationRuleLog"
  | extend msg_s= column_ifexists('msg_s',Message)
  | parse msg_s with Protocol " request from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int "." *
  | where isnotempty(DestinationPort)
  | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = Protocol, SourceIp, Fqdn);
  let LearningPortToProtocol2 = (AZFWNetworkRule
  | where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
  | where isnotempty(DestinationPort)
  | extend Fqdn = DestinationIp
  | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = Protocol, SourceIp, Fqdn);
  let LearningPortToProtocol3 = (AZFWApplicationRule
  | where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
  | where isnotempty(DestinationPort)
  | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = Protocol, SourceIp, Fqdn);
  let AlertTimePortToProtocol1 = (AzureDiagnostics
  | where TimeGenerated between (DetectionWindowStart .. now())
  | where OperationName == "AzureFirewallApplicationRuleLog"
  | extend msg_s= column_ifexists('msg_s',Message)
  | parse msg_s with Protocol " request from " SourceIp ":" SourcePort " to " Fqdn ":" DestinationPort:int "." *
  | where isnotempty(DestinationPort)
  | summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = Protocol);
  let AlertTimePortToProtocol2 = (AZFWNetworkRule
  | where TimeGenerated between (DetectionWindowStart .. now())
  | where isnotempty(DestinationPort)
  | extend Fqdn = DestinationIp
  | summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = Protocol);
  let AlertTimePortToProtocol3 = (AZFWApplicationRule
  | where TimeGenerated between (DetectionWindowStart .. now())
  | where isnotempty(DestinationPort)
  | summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = Protocol);
  (union isfuzzy=true 
  (AlertTimePortToProtocol1 
  | join kind=leftouter (LearningPortToProtocol1) on $left.AlertTimeDstPort == $right.LearningTimeDstPort
  | where LearningTimeProtocol != AlertTimeProtocol),
  (AlertTimePortToProtocol2 
  | join kind=leftanti (LearningPortToProtocol2) on $left.AlertTimeDstPort == $right.LearningTimeDstPort, $left.AlertTimeProtocol == $right.LearningTimeProtocol),
  (AlertTimePortToProtocol3 
  | join kind=leftouter (LearningPortToProtocol3) on $left.AlertTimeDstPort == $right.LearningTimeDstPort
  | where LearningTimeProtocol != AlertTimeProtocol))
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Fqdn
customDetails:
  AbnormalPort: AlertTimeDstPort
  NetworkProtocol: AlertTimeProtocol
alertDetailsOverride:
  alertDisplayNameFormat: 'Abnormal Port to Protocol Communication Detected from {{SourceIp}} to {{Fqdn}}'
  alertDescriptionFormat: 'Communication was observed over port {{AlertTimeDstPort}} using protocol {{AlertTimeProtocol}}, which is different from the protocol observed during the learning period. This could indicate potential malicious activity such as C2 communication or data exfiltration.'
version: 1.1.5
kind: Scheduled

Stages and Predicates

Stage 0: let

let LearningPeriod = 7d;
let RunTime = 1h;
let StartLearningPeriod = LearningPeriod + RunTime;
let DetectionWindowStart = ago(RunTime);
let LearningPortToProtocol1 =  (AzureDiagnostics
| where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
| where OperationName == "AzureFirewallApplicationRuleLog"
| extend msg_s= column_ifexists('msg_s',Message)
| parse msg_s with Protocol " request from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int "." *
| where isnotempty(DestinationPort)
| summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = Protocol, SourceIp, Fqdn);
let LearningPortToProtocol2 = (AZFWNetworkRule
| where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
| where isnotempty(DestinationPort)
| extend Fqdn = DestinationIp
| summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = Protocol, SourceIp, Fqdn);
let LearningPortToProtocol3 = (AZFWApplicationRule
| where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
| where isnotempty(DestinationPort)
| summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = Protocol, SourceIp, Fqdn);
let AlertTimePortToProtocol1 = (AzureDiagnostics <inlined as stages below>;
let AlertTimePortToProtocol2 = (AZFWNetworkRule <inlined as stages below>;
let AlertTimePortToProtocol3 = (AZFWApplicationRule <inlined as stages below>;

Stage 1: union

union of 3 branches

Stage 2: source

AzureDiagnostics

Stage 3: where

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

Stage 4: where

| where OperationName == "AzureFirewallApplicationRuleLog"

Stage 5: extend

| extend msg_s= column_ifexists('msg_s',Message)

Stage 6: parse

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

Stage 7: where

| where isnotempty(DestinationPort)

Stage 8: summarize

| summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = Protocol

Stage 9: join

| join kind=leftouter (LearningPortToProtocol1) on $left.AlertTimeDstPort == $right.LearningTimeDstPort

Stage 10: where

where LearningTimeProtocol != AlertTimeProtocol

Stage 11: source

AZFWNetworkRule

Stage 12: where

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

Stage 13: where

| where isnotempty(DestinationPort)

Stage 14: extend

| extend Fqdn = DestinationIp

Stage 15: summarize

| summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = Protocol

Stage 16: join (negated)

join kind=leftanti (LearningPortToProtocol2) on AlertTimeDstPort, LearningTimeDstPort, AlertTimeProtocol, LearningTimeProtocol

Stage 17: source

AZFWApplicationRule

Stage 18: where

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

Stage 19: where

| where isnotempty(DestinationPort)

Stage 20: summarize

| summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = Protocol

Stage 21: join

| join kind=leftouter (LearningPortToProtocol3) on $left.AlertTimeDstPort == $right.LearningTimeDstPort

Stage 22: where

where LearningTimeProtocol != AlertTimeProtocol

Stage 23: summarize aggregation inside the join branch

summarize by LearningTimeDstPort, LearningTimeProtocol, SourceIp, Fqdn

Stage 24: summarize aggregation inside the join branch

summarize by LearningTimeDstPort, LearningTimeProtocol, SourceIp, Fqdn

Stage 25: summarize aggregation inside the join branch

summarize by LearningTimeDstPort, LearningTimeProtocol, SourceIp, Fqdn

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DestinationPortis_not_null(no value, null check)excludes:DestinationPort

Output fields

These fields are emitted when the rule matches.

FieldSource
Fqdnsummarize
LearningTimeDstPortsummarize
LearningTimeProtocolsummarize
SourceIpsummarize