Detection rules › Kusto

Tailscale: Tailnet lock validation failed

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

Identifies tailnet devices with a non-empty TailnetLockError, indicating the device failed tailnet-lock cryptographic validation. Suspicious - likely an unsigned node attempting to join.

MITRE ATT&CK coverage

Rule body

id: e9f0a1b2-3456-7890-12cd-ef1234560040
name: "Tailscale: Tailnet lock validation failed"
description: Identifies tailnet devices with a non-empty TailnetLockError, indicating the device failed tailnet-lock cryptographic validation. Suspicious - likely an unsigned node attempting to join.
description-detailed: |
  Identifies tailnet devices with a non-empty TailnetLockError, indicating the device failed tailnet-lock cryptographic validation. Tailnet lock requires new node-keys be co-signed by trusted signers; errors here are the only direct signal of a node-key injection attempt.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Devices_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - InitialAccess
relevantTechniques:
  - T1556
  - T1078
query: |
  Tailscale_Devices_CL
  | where TimeGenerated > ago(1h)
  | summarize arg_max(TimeGenerated, *) by DeviceId
  | where isnotempty(TailnetLockError)
  | project TimeGenerated, DeviceName, Hostname, User, Os, ClientVersion, TailnetLockError, TailnetLockKey, LastSeen, Authorized
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

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 isnotempty(TailnetLockError)

Stage 5: project

| project TimeGenerated, DeviceName, Hostname, User, Os, ClientVersion, TailnetLockError, TailnetLockKey, LastSeen, Authorized

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
TailnetLockErroris_not_null
  • (no value, null check)
field:"TailnetLockError" kind:is_not_null

Output fields

These fields are emitted when the rule matches.

FieldSource
Authorizedproject
ClientVersionproject
DeviceNameproject
Hostnameproject
LastSeenproject
Osproject
TailnetLockErrorproject
TailnetLockKeyproject
TimeGeneratedproject
Userproject