Detection rules › Kusto
Detect port misuse by anomaly based detection (ASIM Network Session schema)
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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Command & Control |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | cross_field_compare |
| field:"Action" kind:cross_field_compare value:"DvcAction" |
Action | eq |
| field:"Action" kind:eq value:"*" |
Direction | cross_field_compare |
| field:"Direction" kind:cross_field_compare value:"NetworkDirection" |
Direction | eq |
| field:"Direction" kind:eq value:"*" |
DstPortNumber | is_not_null | field:"DstPortNumber" kind:is_not_null | |
Protocol | cross_field_compare |
| field:"Protocol" kind:cross_field_compare value:"NetworkProtocol" |
Protocol | eq |
| field:"Protocol" kind:eq value:"*" |
Severity | ne |
| field:"Severity" kind:ne value:"Disabled" |
ThresholdType | eq |
| field:"ThresholdType" kind:eq value:"Anomaly" |
TimeGenerated | cross_field_compare |
| field:"TimeGenerated" kind:cross_field_compare value:"maxSummarizedTime" |
Type | eq |
| field:"Type" kind:eq value:"Detection" |
baseline | gt |
| field:"baseline" kind:gt value:"0" |
score | ge |
| field:"score" kind:ge value:"5" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Description | summarize |
DstPortNumber | summarize |
DvcActions | summarize |
Name | summarize |
NetworkDirections | summarize |
NetworkProtocols | summarize |
Severity | summarize |
Tactic | summarize |