Detection rules › Kusto

Detect port misuse by static threshold (ASIM Network Session schema)

Status
available
Severity
medium
Time window
1h
Group by
Description, DstPortNumber, DvcAction, Name, NetworkDirection, NetworkProtocol, Severity, Tactic
Source
github.com/Azure/Azure-Sentinel

'This detection rule detects port usage above the configured threshold. 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

Rule body kusto

id: 156997bd-da0f-4729-b47a-0a3e02dd50c8
name: Detect port misuse by static threshold (ASIM Network Session schema)
description: |
  'This detection rule detects port usage above the configured threshold. 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: 20m
queryPeriod: 60m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Execution
  - InitialAccess
relevantTechniques:
  - T1095  
  - T1059
  - T1203
  - T1190
query: |
  let lookback = 20m; 
  let mapping = _GetWatchlist('NetworkSession_Monitor_Configuration')
      | where Type == "Detection" and ThresholdType == "Static" 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 NetworkSummary_Protocol = materialize(
      union isfuzzy=true 
          (
          NetworkSummary_Protocol_CL
          | 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
          | 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
  ]
          | join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists
          | join (NetworkCustomAnalytics) on NetworkCustomAnalyticsExists
          )
          | join (
              _Im_NetworkSession(starttime=bin(now(-20m), 20m), endtime=bin(now(), 20m))
              | where TimeGenerated > bin(now(-20m), 20m)
              | summarize Count=count()
                  by
                  NetworkProtocol,
                  DstPortNumber,
                  DstAppName,
                  NetworkDirection,
                  DvcAction,
                  bin(TimeGenerated, 20m)
              | 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 == toscalar(NetworkCustomAnalytics_protocol_CL
                  | summarize max(EventTime_t))
              | 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 == toscalar(NetworkSummary_Protocol_CL
              | summarize max(EventTime))
          | 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)
  | summarize Sum=sum(Count) by DstPortNumber, NetworkProtocol, NetworkDirection, DvcAction 
  | join kind=inner ['mapping'] where Ports has tostring(DstPortNumber)
  | where Sum > Threshold         
      and (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.2.0
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 20m;

Let binding: mapping

let mapping = _GetWatchlist('NetworkSession_Monitor_Configuration')
    | where Type == "Detection" and ThresholdType == "Static" 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 binding: NetworkSummary_Protocol

let NetworkSummary_Protocol = materialize(
    union isfuzzy=true 
        (
        NetworkSummary_Protocol_CL
        | project v = int(2)
        ),
        (
        print int(1) 
        | project v = print_0
        )
    | summarize maxv = max(v)
    | extend
        NetworkSummary_Protocol_Exists = (maxv > 1),
        NetworkCustomAnalyticsExists = false
    );

Let binding: NetworkCustomAnalytics

let NetworkCustomAnalytics = materialize(
    union isfuzzy=true 
        (
        NetworkCustomAnalytics_protocol_CL
        | project v = int(2)
        ),
        (
        print int(1) 
        | project v = print_0
        )
    | summarize maxv = max(v)
    | extend
        NetworkCustomAnalyticsExists = (maxv > 1),
        NetworkSummary_Protocol_Exists = false
    );

union isfuzzy=true (3 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: datatable(, datatable(exists:, NetworkSummary_Protocol_CL

Leg 1: datatable(

(datatable(
    exists: int,
    NetworkSummary_Protocol_Exists: bool,
    NetworkCustomAnalyticsExists: bool
)[
    1, false, false
]
        | join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists
        | join (NetworkCustomAnalytics) on NetworkCustomAnalyticsExists
        )
        | join (
            _Im_NetworkSession(starttime=bin(now(-20m), 20m), endtime=bin(now(), 20m))
            | where TimeGenerated > bin(now(-20m), 20m)
            | summarize Count=count()
                by
                NetworkProtocol,
                DstPortNumber,
                DstAppName,
                NetworkDirection,
                DvcAction,
                bin(TimeGenerated, 20m)
            | extend
                EventTime = TimeGenerated,
                Count = toint(Count),
                DstPortNumber = toint(DstPortNumber),
                exists=int(1)
            )
            on exists
        | project-away exists*, maxv, NetworkSummary_Protocol_Exists*, NetworkCustomAnalyticsExists*

Leg 2: datatable(exists:

(datatable(exists: int, NetworkSummary_Protocol_Exists: bool)[1, false]
        | join (NetworkSummary_Protocol) on NetworkSummary_Protocol_Exists
        )
        | join (
            NetworkCustomAnalytics_protocol_CL
            | where EventTime_t == toscalar(NetworkCustomAnalytics_protocol_CL
                | summarize max(EventTime_t))
            | 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*

Leg 3: NetworkSummary_Protocol_CL

NetworkSummary_Protocol_CL
        | where EventTime == toscalar(NetworkSummary_Protocol_CL
            | summarize max(EventTime))
        | project
            NetworkProtocol,
            DstPortNumber,
            DstAppName,
            NetworkDirection,
            DvcAction,
            Count=count_,
            EventTime,
            TimeGenerated,
            Type
        | extend Count = toint(Count), DstPortNumber = toint(DstPortNumber)

Applied to the combined result

| project-away exists*, maxv*, NetworkSummary_Protocol_Exists*, NetworkCustomAnalyticsExists* | where isnotempty(DstPortNumber) | summarize Sum=sum(Count) by DstPortNumber, NetworkProtocol, NetworkDirection, DvcAction | join kind=inner ['mapping'] where Ports has tostring(DstPortNumber) | where Sum > Threshold         
    and (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

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
Actioneq
  • * transforms: cased
Actionmatch
  • DvcAction transforms: term
Directioneq
  • * transforms: cased
Directionmatch
  • NetworkDirection transforms: term
DstPortNumberis_not_null
  • (no value, null check)
Protocoleq
  • * transforms: cased
Protocolmatch
  • NetworkProtocol transforms: term
Severityne
  • Disabled transforms: cased
Sumgt
  • Threshold transforms: cased
ThresholdTypeeq
  • Static transforms: cased
Typeeq
  • Detection transforms: cased

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
Descriptionsummarize
DstPortNumbersummarize
DvcActionssummarize
Namesummarize
NetworkDirectionssummarize
NetworkProtocolssummarize
Severitysummarize
Tacticsummarize