Detection rules › Kusto
Tailscale Premium: Network flow beaconing detected
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
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
BeaconPercent | ge |
| field:"BeaconPercent" kind:ge value:"80.0" |
DeltaSec | gt |
| field:"DeltaSec" kind:gt value:"5" |
Dst | cross_field_compare |
| field:"Dst" kind:cross_field_compare value:"NextDst" |
Proto | cross_field_compare |
| field:"Proto" kind:cross_field_compare value:"NextProto" |
Src | cross_field_compare |
| field:"Src" kind:cross_field_compare value:"NextSrc" |
TotalFlows | ge |
| field:"TotalFlows" kind:ge value:"10" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Dst | summarize |
DstNodeName | summarize |
DstUser | summarize |
MostFrequentDeltaCount | summarize |
MostFrequentDeltaSec | summarize |
Proto | summarize |
Src | summarize |
SrcNodeName | summarize |
SrcUser | summarize |
TotalFlows | summarize |
BeaconPercent | extend |