Detection rules › Kusto
Tailscale Premium: Subnet router throughput anomaly
Identifies when a subnet router (gateway node bridging the tailnet to an on-prem or cloud subnet) handles 3x or more its 7-day baseline traffic in the last hour. Spikes can indicate exfiltration or scanning. Requires Tailscale Premium or Enterprise.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule body
id: a5b6c7d8-5e6f-7a8b-9c0d-1e2f3a4b5c6d
name: "Tailscale Premium: Subnet router throughput anomaly"
description: |
Identifies when a subnet router (gateway node bridging the tailnet to an on-prem or cloud subnet) handles 3x or more its 7-day baseline traffic in the last hour. Spikes can indicate exfiltration or scanning. Requires Tailscale Premium or Enterprise.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: TailscalePremiumCCF
dataTypes:
- Tailscale_Network_CL
queryFrequency: 1h
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1572
- T1041
query: |
let baselineDays = 7d;
let recent = 1h;
let multiplier = 3.0;
let recentTraffic =
Tailscale_Network_CL
| where TimeGenerated > ago(recent)
| where HasSubnetTraffic
| mv-expand t = SubnetTraffic
| extend Bytes = tolong(t.txBytes) + tolong(t.rxBytes)
| summarize RecentBytes = sum(Bytes) by NodeId, SrcNodeName, SrcUser, SrcOs, SrcTags=tostring(SrcTags);
let baseline =
Tailscale_Network_CL
| where TimeGenerated between (ago(baselineDays + recent) .. ago(recent))
| where HasSubnetTraffic
| mv-expand t = SubnetTraffic
| extend Bytes = tolong(t.txBytes) + tolong(t.rxBytes)
| summarize TotalBaselineBytes = sum(Bytes) by NodeId
| extend BaselineHourlyBytes = TotalBaselineBytes / 168.0;
recentTraffic
| join kind=inner baseline on NodeId
| where RecentBytes > BaselineHourlyBytes * multiplier
| extend Multiplier = round(RecentBytes / BaselineHourlyBytes, 1), RecentMB = round(RecentBytes / 1024.0 / 1024.0, 2), BaselineHourlyMB = round(BaselineHourlyBytes / 1024.0 / 1024.0, 2)
| project NodeId, SrcNodeName, SrcUser, SrcOs, SrcTags, RecentMB, BaselineHourlyMB, Multiplier
| order by Multiplier desc
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcNodeName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUser
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Parameters
let baselineDays = 7d;
let recent = 1h;
let multiplier = 3.0;
let recentTraffic is inlined into the numbered stages below.
Let binding: baseline
let baseline = Tailscale_Network_CL
| where TimeGenerated between (ago(baselineDays + recent) .. ago(recent))
| where HasSubnetTraffic
| mv-expand t = SubnetTraffic
| extend Bytes = tolong(t.txBytes) + tolong(t.rxBytes)
| summarize TotalBaselineBytes = sum(Bytes) by NodeId
| extend BaselineHourlyBytes = TotalBaselineBytes / 168.0;
Stages 1 to 6 define let recentTraffic (the rule's main pipeline source); stages 7 to 11 run on it.
Stage 1: source
Tailscale_Network_CL
Stage 2: where
| where TimeGenerated > ago(recent)
Stage 3: where
| where HasSubnetTraffic
Stage 4: mv-expand
| mv-expand t = SubnetTraffic
Stage 5: extend
| extend Bytes = tolong(t.txBytes) + tolong(t.rxBytes)
Stage 6: summarize
| summarize RecentBytes = sum(Bytes) by NodeId, SrcNodeName, SrcUser, SrcOs, SrcTags=tostring(SrcTags)
Stage 7: join
recentTraffic
| join kind=inner baseline on NodeId
Stage 8: where
| where RecentBytes > BaselineHourlyBytes * multiplier
Stage 9: extend
| extend Multiplier = round(RecentBytes / BaselineHourlyBytes, 1), RecentMB = round(RecentBytes / 1024.0 / 1024.0, 2), BaselineHourlyMB = round(BaselineHourlyBytes / 1024.0 / 1024.0, 2)
Stage 10: project
| project NodeId, SrcNodeName, SrcUser, SrcOs, SrcTags, RecentMB, BaselineHourlyMB, Multiplier
Stage 11: sort
| order by Multiplier desc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
RecentBytes | cross_field_compare |
| field:"RecentBytes" kind:cross_field_compare value:"BaselineHourlyBytes" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
BaselineHourlyMB | project |
Multiplier | project |
NodeId | project |
RecentMB | project |
SrcNodeName | project |
SrcOs | project |
SrcTags | project |
SrcUser | project |