Detection rules › Kusto

Tailscale: Device started advertising subnet routes

Status
available
Severity
medium
Time window
1d
Group by
DeviceId
Source
github.com/Azure/Azure-Sentinel

Identifies when a tailnet device begins advertising subnet routes (subnet-router capability) not present in the previous snapshot. Unexpected advertisement may indicate a compromised node expanding reachable surface area or an unsanctioned admin change.

MITRE ATT&CK coverage

TacticTechniques
Persistence
Lateral Movement

Rule body

id: c2b3d4e5-2345-6789-01ab-cdef12345002
name: "Tailscale: Device started advertising subnet routes"
description: |
  Identifies when a tailnet device begins advertising subnet routes (subnet-router capability) not present in the previous snapshot. Unexpected advertisement may indicate a compromised node expanding reachable surface area or an unsanctioned admin change.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Devices_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
  - Persistence
relevantTechniques:
  - T1021
  - T1556
query: |
  let recent =
      Tailscale_Devices_CL
      | where TimeGenerated > ago(1h)
      | summarize arg_max(TimeGenerated, *) by DeviceId
      | where array_length(AdvertisedRoutes) > 0;
  let baseline =
      Tailscale_Devices_CL
      | where TimeGenerated between (ago(1d + 1h) .. ago(1h))
      | summarize arg_max(TimeGenerated, *) by DeviceId
      | where array_length(AdvertisedRoutes) > 0
      | distinct DeviceId;
  recent
  | join kind=leftanti baseline on DeviceId
  | project TimeGenerated, DeviceId, DeviceName, Hostname, User, AdvertisedRoutes, EnabledRoutes, LastSeen
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

let recent is inlined into the numbered stages below.

Let binding: baseline used in Stage 5

let baseline = Tailscale_Devices_CL
    | where TimeGenerated between (ago(1d + 1h) .. ago(1h))
    | summarize arg_max(TimeGenerated, *) by DeviceId
    | where array_length(AdvertisedRoutes) > 0
    | distinct DeviceId;

Stages 1 to 4 define let recent (the rule's main pipeline source); stages 5 to 6 run on it.

Stage 1: source

Tailscale_Devices_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: summarize

| summarize arg_max(TimeGenerated, *) by DeviceId

Stage 4: where

| where array_length(AdvertisedRoutes) > 0

Stage 5: join (negated)

recent
| join kind=leftanti baseline on DeviceId

Stage 6: project

| project TimeGenerated, DeviceId, DeviceName, Hostname, User, AdvertisedRoutes, EnabledRoutes, LastSeen

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
AdvertisedRoutesgt0excludes:AdvertisedRoutes field:"AdvertisedRoutes" value:"0"

Output fields

These fields are emitted when the rule matches.

FieldSource
AdvertisedRoutesproject
DeviceIdproject
DeviceNameproject
EnabledRoutesproject
Hostnameproject
LastSeenproject
TimeGeneratedproject
Userproject