Detection rules › Kusto

Tailscale Premium: Network flow beaconing detected

Status
available
Severity
medium
Time window
2d
Group by
DeltaSec, Dst, DstNodeName, DstUser, Proto, Src, SrcNodeName, SrcUser
Source
github.com/Azure/Azure-Sentinel

Identifies when flows between a src-dst pair recur at a regular interval (80%+ of inter-flow gaps cluster on the same delta over 10+ flows). Signature of C2 beaconing or scheduled exfiltration. Requires Tailscale Premium or Enterprise.

MITRE ATT&CK coverage

Rule body

id: e3f4a5b6-3c4d-5e6f-7a8b-9c0d1e2f3a4b
name: "Tailscale Premium: Network flow beaconing detected"
description: |
  Identifies when flows between a src-dst pair recur at a regular interval (80%+ of inter-flow gaps cluster on the same delta over 10+ flows). Signature of C2 beaconing or scheduled exfiltration. Requires Tailscale Premium or Enterprise.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: TailscalePremiumCCF
    dataTypes:
      - Tailscale_Network_CL
queryFrequency: 1h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1071
  - T1095
  - T1029
query: |
  let lookback = 2d;
  let minFlows = 10;
  let beaconPercentThreshold = 80.0;
  Tailscale_Network_CL
  | where TimeGenerated > ago(lookback)
  | where HasVirtualTraffic
  | mv-expand t = VirtualTraffic
  | extend Src = tostring(t.src), Dst = tostring(t.dst), Proto = toint(t.proto)
  | project TimeGenerated, Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser
  | sort by Src asc, Dst asc, Proto asc, TimeGenerated asc
  | serialize
  | extend NextTime = next(TimeGenerated), NextSrc = next(Src), NextDst = next(Dst), NextProto = next(Proto)
  | where Src == NextSrc and Dst == NextDst and Proto == NextProto
  | extend DeltaSec = datetime_diff('second', NextTime, TimeGenerated)
  | where DeltaSec > 5
  | summarize DeltaCount = count() by Src, Dst, Proto, DeltaSec, SrcNodeName, SrcUser, DstNodeName, DstUser
  | summarize (MostFrequentDeltaCount, MostFrequentDeltaSec) = arg_max(DeltaCount, DeltaSec), TotalFlows = sum(DeltaCount) by Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser
  | where TotalFlows >= minFlows
  | extend BeaconPercent = round(MostFrequentDeltaCount * 100.0 / TotalFlows, 1)
  | where BeaconPercent >= beaconPercentThreshold
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcNodeName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DstNodeName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SrcUser
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Src
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Parameters

let lookback = 2d;
let minFlows = 10;
let beaconPercentThreshold = 80.0;

Stage 1: source

Tailscale_Network_CL

Stage 2: where

| where TimeGenerated > ago(lookback)

Stage 3: where

| where HasVirtualTraffic

Stage 4: mv-expand

| mv-expand t = VirtualTraffic

Stage 5: extend

| extend Src = tostring(t.src), Dst = tostring(t.dst), Proto = toint(t.proto)

Stage 6: project

| project TimeGenerated, Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser

Stage 7: sort

| sort by Src asc, Dst asc, Proto asc, TimeGenerated asc

Stage 8: kusto:serialize

| serialize

Stage 9: extend

| extend NextTime = next(TimeGenerated), NextSrc = next(Src), NextDst = next(Dst), NextProto = next(Proto)

Stage 10: where

| where Src == NextSrc and Dst == NextDst and Proto == NextProto

Stage 11: extend

| extend DeltaSec = datetime_diff('second', NextTime, TimeGenerated)

Stage 12: where

| where DeltaSec > 5

Stage 13: summarize

| summarize DeltaCount = count() by Src, Dst, Proto, DeltaSec, SrcNodeName, SrcUser, DstNodeName, DstUser

Stage 14: summarize

| summarize (MostFrequentDeltaCount, MostFrequentDeltaSec) = arg_max(DeltaCount, DeltaSec), TotalFlows = sum(DeltaCount) by Src, Dst, Proto, SrcNodeName, SrcUser, DstNodeName, DstUser

Stage 15: where

| where TotalFlows >= minFlows

Stage 16: extend

| extend BeaconPercent = round(MostFrequentDeltaCount * 100.0 / TotalFlows, 1)

Stage 17: where

| where BeaconPercent >= beaconPercentThreshold

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
Dstsummarize
DstNodeNamesummarize
DstUsersummarize
MostFrequentDeltaCountsummarize
MostFrequentDeltaSecsummarize
Protosummarize
Srcsummarize
SrcNodeNamesummarize
SrcUsersummarize
TotalFlowssummarize
BeaconPercentextend