Detection rules › Kusto

Detect port misuse by anomaly based detection (ASIM Network Session schema)

Status
available
Severity
medium
Time window
14d
Group by
Description, DstPortNumber, DvcAction, Name, NetworkCustomAnalyticsExists, NetworkDirection, NetworkProtocol, NetworkSummary_Protocol_Exists, Severity, Tactic, exists
Source
github.com/Azure/Azure-Sentinel

This rule detects anomalous pattern in port usage. The rule utilize ASIM normalization, and is applied to any source which supports the ASIM Network Session schema. To tune the rule to your environment configure it using the 'NetworkSession_Monitor_Configuration' watchlist. This rule leverages log summaries generated by a Summary Rule or Summarized Playbook. If no such summaries are available, the rule falls back to direct analysis using ASIM function.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: cbf07406-fa2a-48b0-82b8-efad58db14ec
name: Detect port misuse by anomaly based detection (ASIM Network Session schema)
description: |
  'This rule detects anomalous pattern in port usage. The rule utilize [ASIM](https://aka.ms/AboutASIM) normalization, and is applied to any source which supports the ASIM Network Session schema. To tune the rule to your environment configure it using the 'NetworkSession_Monitor_Configuration' watchlist. This rule leverages log summaries generated by a Summary Rule or Summarized Playbook. If no such summaries are available, the rule falls back to direct analysis using ASIM function.'
severity: Medium
status: Available
tags:
  - Schema: ASimNetworkSessions
    SchemaVersion: 0.2.4
requiredDataConnectors:
  - connectorId: AWSS3
    dataTypes:
      - AWSVPCFlow
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceNetworkEvents
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: MicrosoftSysmonForLinux
    dataTypes:
      - Syslog
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: AzureMonitor(VMInsights)
    dataTypes:
      - VMConnection
  - connectorId: AzureFirewall
    dataTypes:
      - AzureDiagnostics
  - connectorId: AzureNSG
    dataTypes:
      - AzureDiagnostics
  - connectorId: CiscoASA
    dataTypes:
      - CommonSecurityLog
  - connectorId: CiscoAsaAma
    dataTypes:
      - CommonSecurityLog
  - connectorId: Corelight
    dataTypes:
      - Corelight_CL
  - connectorId: AIVectraStream
    dataTypes:
      - VectraStream
  - connectorId: CheckPoint
    dataTypes:
      - CommonSecurityLog
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
  - connectorId: CiscoMeraki
    dataTypes:
      - Syslog
      - CiscoMerakiNativePoller

queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - LateralMovement
  - Execution
  - InitialAccess
relevantTechniques:
  - T1095  
  - T1059
  - T1203
  - T1190
query: |
  let lookback = 14d;
  let mapping = _GetWatchlist('NetworkSession_Monitor_Configuration')
      | where Type == "Detection" and ThresholdType == "Anomaly" and Severity != "Disabled"  
      | extend
          Ports = split(Ports, ","),
          App = split(App, ","),
          Protocol = split(Protocol, ","),
          Direction = split(Direction, ","),
          Action = split(Action, ",")
      | project
          Ports,
          App,
          Protocol,
          Direction,
          Action,
          Type,
          ThresholdType,
          Threshold,
          Severity,
          Tactic,
          Name,
          Description
      | mv-expand Ports
      | mv-expand App
      | mv-expand Protocol
      | mv-expand Direction
      | mv-expand Action
      | extend
          Ports = tostring(Ports),
          App = tostring(App),
          Protocol = tostring(Protocol),
          Direction = tostring(Direction),
          Action = tostring(Action),
          Threshold = toint(Threshold)
  ;
  let AnomalyThreshold = 2.5;
  let eps = materialize (_Im_NetworkSession
      | project TimeGenerated
      | where TimeGenerated > ago(5m)
      | count
      | extend Count = Count / 300);
  let maxSummarizedTime = toscalar (
      union isfuzzy=true
          (
          NetworkSummary_Protocol_CL
          | where EventTime > ago(lookback)
          | summarize max_TimeGenerated=max(EventTime)
          | extend max_TimeGenerated = datetime_add('minute', 20, max_TimeGenerated)
          ),
          (
          NetworkCustomAnalytics_protocol_CL
          | where EventTime_t > ago(lookback)
          | summarize max_TimeGenerated=max(EventTime_t)
          | extend max_TimeGenerated = datetime_add('minute', 20, max_TimeGenerated)
          ),
          (
          print(ago(lookback))
          | project max_TimeGenerated = print_0
          )
      | summarize maxTimeGenerated = max(max_TimeGenerated) 
      );
  let NetworkSummary_Protocol = materialize(
      union isfuzzy=true 
          (
          NetworkSummary_Protocol_CL
          | where EventTime > ago(1d)
          | project v = int(2)
          ),
          (
          print int(1) 
          | project v = print_0
          )
      | summarize maxv = max(v)
      | extend
          NetworkSummary_Protocol_Exists = (maxv > 1),
          NetworkCustomAnalyticsExists = false
      );
  let NetworkCustomAnalytics = materialize(
      union isfuzzy=true 
          (
          NetworkCustomAnalytics_protocol_CL
          | where EventTime_t > ago(1d) 
          | project v = int(2)
          ),
          (
          print int(1) 
          | project v = print_0
          )
      | summarize maxv = max(v)
      | extend
          NetworkCustomAnalyticsExists = (maxv > 1),
          NetworkSummary_Protocol_Exists = false
      );
  let allData = union isfuzzy=true 
          (
          (datatable(
      exists: int,
      NetworkSummary_Protocol_Exists: bool,
      NetworkCustomAnalyticsExists: bool
  )[
      1, false, false
  ]
          | where toscalar(eps) > 500
          | join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists
          | join (NetworkCustomAnalytics) on NetworkCustomAnalyticsExists
          )
          | join (
              _Im_NetworkSession(starttime=todatetime(ago(3d)), endtime=now())
              | where TimeGenerated > maxSummarizedTime
              | summarize Count=count()
                  by
                  NetworkProtocol,
                  DstPortNumber,
                  DstAppName,
                  NetworkDirection,
                  DvcAction,
                  bin(TimeGenerated, 10m)
              | extend
                  EventTime = TimeGenerated,
                  Count = toint(Count),
                  DstPortNumber = toint(DstPortNumber),
                  exists=int(1)
              )
              on exists
          | project-away exists*, maxv, NetworkSummary_Protocol_Exists*, NetworkCustomAnalyticsExists*
          ),
          (
          (datatable(
      exists: int,
      NetworkSummary_Protocol_Exists: bool,
      NetworkCustomAnalyticsExists: bool
  )[
      1, false, false
  ]
          | where toscalar(eps) <= 500
          | join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists
          | join (NetworkCustomAnalytics) on NetworkCustomAnalyticsExists
          )
          | join (
              _Im_NetworkSession(starttime=todatetime(ago(4d)), endtime=now())
              | where TimeGenerated > maxSummarizedTime
              | summarize Count=count()
                  by
                  NetworkProtocol,
                  DstPortNumber,
                  DstAppName,
                  NetworkDirection,
                  DvcAction,
                  bin(TimeGenerated, 10m)
              | extend
                  EventTime = TimeGenerated,
                  Count = toint(Count),
                  DstPortNumber = toint(DstPortNumber),
                  exists=int(1)
              )
              on exists
          | project-away exists*, maxv, NetworkSummary_Protocol_Exists*, NetworkCustomAnalyticsExists*
          ),
          (
          (datatable(exists: int, NetworkSummary_Protocol_Exists: bool)[1, false]
          | join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists
          )
          | join (
              NetworkCustomAnalytics_protocol_CL
              | where EventTime_t > ago(lookback)
              | project
                  NetworkProtocol=NetworkProtocol_s,
                  DstPortNumber=DstPortNumber_d,
                  DstAppName=DstAppName_s,
                  NetworkDirection=NetworkDirection_s,
                  DvcAction=DvcAction_s,
                  Count=count__d,
                  EventTime=EventTime_t,
                  TimeGenerated,
                  Type
              | extend Count = toint(Count), DstPortNumber = toint(DstPortNumber), exists=int(1)
              )
              on exists
          | project-away exists*, maxv, NetworkSummary_Protocol_Exists*, NetworkCustomAnalyticsExists*
          ),
          (
          NetworkSummary_Protocol_CL
          | where EventTime > ago(lookback)
          | project
              NetworkProtocol,
              DstPortNumber,
              DstAppName,
              NetworkDirection,
              DvcAction,
              Count=count_,
              EventTime,
              TimeGenerated,
              Type
          | extend Count = toint(Count), DstPortNumber = toint(DstPortNumber)
          )
      | project-away exists*, maxv*, NetworkSummary_Protocol_Exists*, NetworkCustomAnalyticsExists*
  ;
  allData
  | where isnotempty(DstPortNumber)
  | make-series Total=sum(Count) on EventTime from ago(lookback) to now() step 1d by DstPortNumber, NetworkProtocol, NetworkDirection, DvcAction
  | extend (anomalies, score, baseline) = series_decompose_anomalies(Total, AnomalyThreshold, -1, 'linefit')
  | mv-expand anomalies, score, baseline, EventTime, Total
  | extend
      anomalies = toint(anomalies),
      score = toint(score),
      baseline = toint(baseline),
      EventTime = todatetime(EventTime),
      Total = tolong(Total)
  | where EventTime >= ago(1d)
  | extend DstPortNumber = tostring(DstPortNumber)
  | where score >= 2 * AnomalyThreshold and baseline > 0
  | join kind=inner ['mapping'] where Ports == DstPortNumber
  | where (Protocol == "*" or Protocol has NetworkProtocol)
      and (Direction == "*" or Direction has NetworkDirection)
      and (Action == "*" or Action has DvcAction)  
  | project
      Name,
      Description,
      NetworkProtocol,
      DstPortNumber,
      NetworkDirection,
      DvcAction,
      Severity,
      Tactic
  | summarize
      NetworkProtocols=make_set_if(NetworkProtocol, isnotempty(NetworkProtocol), 20), 
      NetworkDirections=make_set_if(NetworkDirection, isnotempty(NetworkDirection), 5), 
      DvcActions=make_set_if(DvcAction, isnotempty(DvcAction), 10)
      by Name, Severity, Tactic, DstPortNumber, Description
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  AllNetworkProtocols: NetworkProtocols
  AllNetworkDirections: NetworkDirections
  AllDvcAction: DvcActions
  DstPortNumber: DstPortNumber
alertDetailsOverride:
  alertDisplayNameFormat: Detected {{Name}}
  alertDescriptionFormat: '{{Description}}'
  alertTacticsColumnName: Tactic
  alertSeverityColumnName: Severity
version: 1.3.0
kind: Scheduled

Stages and Predicates

Stage 0: let

let lookback = 14d;
let mapping = _GetWatchlist('NetworkSession_Monitor_Configuration')
    | where Type == "Detection" and ThresholdType == "Anomaly" and Severity != "Disabled"  
    | extend
        Ports = split(Ports, ","),
        App = split(App, ","),
        Protocol = split(Protocol, ","),
        Direction = split(Direction, ","),
        Action = split(Action, ",")
    | project
        Ports,
        App,
        Protocol,
        Direction,
        Action,
        Type,
        ThresholdType,
        Threshold,
        Severity,
        Tactic,
        Name,
        Description
    | mv-expand Ports
    | mv-expand App
    | mv-expand Protocol
    | mv-expand Direction
    | mv-expand Action
    | extend
        Ports = tostring(Ports),
        App = tostring(App),
        Protocol = tostring(Protocol),
        Direction = tostring(Direction),
        Action = tostring(Action),
        Threshold = toint(Threshold)
;
let AnomalyThreshold = 2.5;
let eps = materialize (_Im_NetworkSession
    | project TimeGenerated
    | where TimeGenerated > ago(5m)
    | count
    | extend Count = Count / 300);
let maxSummarizedTime = toscalar (
    union isfuzzy=true
        (
        NetworkSummary_Protocol_CL
        | where EventTime > ago(lookback)
        | summarize max_TimeGenerated=max(EventTime)
        | extend max_TimeGenerated = datetime_add('minute', 20, max_TimeGenerated)
        ),
        (
        NetworkCustomAnalytics_protocol_CL
        | where EventTime_t > ago(lookback)
        | summarize max_TimeGenerated=max(EventTime_t)
        | extend max_TimeGenerated = datetime_add('minute', 20, max_TimeGenerated)
        ),
        (
        print(ago(lookback))
        | project max_TimeGenerated = print_0
        )
    | summarize maxTimeGenerated = max(max_TimeGenerated) 
    );
let NetworkSummary_Protocol = materialize(
    union isfuzzy=true 
        (
        NetworkSummary_Protocol_CL
        | where EventTime > ago(1d)
        | project v = int(2)
        ),
        (
        print int(1) 
        | project v = print_0
        )
    | summarize maxv = max(v)
    | extend
        NetworkSummary_Protocol_Exists = (maxv > 1),
        NetworkCustomAnalyticsExists = false
    );
let NetworkCustomAnalytics = materialize(
    union isfuzzy=true 
        (
        NetworkCustomAnalytics_protocol_CL
        | where EventTime_t > ago(1d) 
        | project v = int(2)
        ),
        (
        print int(1) 
        | project v = print_0
        )
    | summarize maxv = max(v)
    | extend
        NetworkCustomAnalyticsExists = (maxv > 1),
        NetworkSummary_Protocol_Exists = false
    );
let allData = union <inlined as stages below>;

Stage 1: source

let allData

Stage 2: union

union of 4 branches

Stage 3: source

datatable

Stage 4: where

where /* macro: (toscalar(eps) > 500) */

Stage 5: join

join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists

Stage 6: join

join (NetworkCustomAnalytics) on NetworkCustomAnalyticsExists

Stage 7: join

join (_Im_NetworkSession) on exists

Stage 8: project-away

project-away NetworkCustomAnalyticsExists*, NetworkSummary_Protocol_Exists*, exists*, maxv

Stage 9: source

datatable

Stage 10: where

where /* macro: (toscalar(eps) <= 500) */

Stage 11: join

join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists

Stage 12: join

join (NetworkCustomAnalytics) on NetworkCustomAnalyticsExists

Stage 13: join

join (_Im_NetworkSession) on exists

Stage 14: project-away

project-away NetworkCustomAnalyticsExists*, NetworkSummary_Protocol_Exists*, exists*, maxv

Stage 15: source

datatable

Stage 16: join

join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists

Stage 17: join

join (NetworkCustomAnalytics_protocol_CL) on exists

Stage 18: project-away

project-away NetworkCustomAnalyticsExists*, NetworkSummary_Protocol_Exists*, exists*, maxv

Stage 19: source

NetworkSummary_Protocol_CL

Stage 20: where

where EventTime > ago(1209600s)

Stage 21: project

project Count, DstAppName, DstPortNumber, DvcAction, EventTime, NetworkDirection, NetworkProtocol, TimeGenerated, Type

Stage 22: extend

extend Count, DstPortNumber

Stage 23: project-away

project-away NetworkCustomAnalyticsExists*, NetworkSummary_Protocol_Exists*, exists*, maxv*

Stage 24: where

where isnotempty(DstPortNumber)

Stage 25: summarize

summarize Total by DstPortNumber, NetworkProtocol, NetworkDirection, DvcAction

Stage 26: extend

extend anomalies, baseline, score

Stage 27: mv-expand

mv-expand anomalies

Stage 28: extend

extend EventTime, Total, anomalies, baseline, score

Stage 29: where

where EventTime >= ago(86400s)

Stage 30: extend

extend DstPortNumber

Stage 31: where

where baseline > 0 and score >= 5

Stage 32: join

join kind=inner (mapping)

Stage 33: where

where (Action =~ DvcAction or Action == "*") and (Direction =~ NetworkDirection or Direction == "*") and (Protocol =~ NetworkProtocol or Protocol == "*")

Stage 34: project

project Description, DstPortNumber, DvcAction, Name, NetworkDirection, NetworkProtocol, Severity, Tactic

Stage 35: summarize

summarize DvcActions, NetworkDirections, NetworkProtocols by Name, Severity, Tactic, DstPortNumber, Description

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncross_field_compare
  • DvcAction transforms: op:match corpus 2 (kusto 2)
field:"Action" kind:cross_field_compare value:"DvcAction"
Actioneq
  • * corpus 3 (kusto 3)
field:"Action" kind:eq value:"*"
Directioncross_field_compare
  • NetworkDirection transforms: op:match corpus 2 (kusto 2)
field:"Direction" kind:cross_field_compare value:"NetworkDirection"
Directioneq
  • * corpus 2 (kusto 2)
field:"Direction" kind:eq value:"*"
DstPortNumberis_not_null
  • (no value, null check)
field:"DstPortNumber" kind:is_not_null
Protocolcross_field_compare
  • NetworkProtocol transforms: op:match corpus 2 (kusto 2)
field:"Protocol" kind:cross_field_compare value:"NetworkProtocol"
Protocoleq
  • * corpus 2 (kusto 2)
field:"Protocol" kind:eq value:"*"
Severityne
  • Disabled corpus 2 (kusto 2)
field:"Severity" kind:ne value:"Disabled"
ThresholdTypeeq
  • Anomaly
field:"ThresholdType" kind:eq value:"Anomaly"
TimeGeneratedcross_field_compare
  • maxSummarizedTime transforms: op:gt corpus 4 (kusto 4)
field:"TimeGenerated" kind:cross_field_compare value:"maxSummarizedTime"
Typeeq
  • Detection corpus 2 (kusto 2)
field:"Type" kind:eq value:"Detection"
baselinegt
  • 0 corpus 5 (kusto 5)
field:"baseline" kind:gt value:"0"
scorege
  • 5 corpus 6 (kusto 6)
field:"score" kind:ge value:"5"

Output fields

These fields are emitted when the rule matches.

FieldSource
Descriptionsummarize
DstPortNumbersummarize
DvcActionssummarize
Namesummarize
NetworkDirectionssummarize
NetworkProtocolssummarize
Severitysummarize
Tacticsummarize