Detection rules › Kusto
Tailscale: DNS nameservers modified
Identifies when the tailnet's global DNS nameserver list is modified. Adding an attacker-controlled resolver as a tailnet-wide nameserver enables broad DNS hijacking for every device using MagicDNS resolution.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access | |
| Command & Control |
Rule body
id: c5d6e7f8-2345-6789-01ab-cdef12345011
name: "Tailscale: DNS nameservers modified"
description: |
Identifies when the tailnet's global DNS nameserver list is modified. Adding an attacker-controlled resolver as a tailnet-wide nameserver enables broad DNS hijacking for every device using MagicDNS resolution.
severity: High
status: Available
requiredDataConnectors:
- connectorId: TailscaleCCF
dataTypes:
- Tailscale_Audit_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- CommandAndControl
relevantTechniques:
- T1556
- T1568
query: |
Tailscale_Audit_CL
| where Action == "UPDATE"
| where tostring(Target.type) == "TAILNET"
| where tostring(Target.property) == "DNS_NAMESERVERS"
| extend ActorLogin = tostring(Actor.loginName)
| extend OldServers = Old.dns
| extend NewServers = New.dns
| extend Added = set_difference(NewServers, OldServers)
| extend Removed = set_difference(OldServers, NewServers)
| project TimeGenerated, ActorLogin, OldServers, NewServers, Added, Removed, Origin
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorLogin
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_Audit_CL
Stage 2: where
| where Action == "UPDATE"
Stage 3: where
| where tostring(Target.type) == "TAILNET"
Stage 4: where
| where tostring(Target.property) == "DNS_NAMESERVERS"
Stage 5: extend (5 consecutive steps)
| extend ActorLogin = tostring(Actor.loginName)
| extend OldServers = Old.dns
| extend NewServers = New.dns
| extend Added = set_difference(NewServers, OldServers)
| extend Removed = set_difference(OldServers, NewServers)
Stage 6: project
| project TimeGenerated, ActorLogin, OldServers, NewServers, Added, Removed, Origin
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"UPDATE" |
property | eq |
| field:"property" kind:eq value:"DNS_NAMESERVERS" |
type | eq |
| field:"type" kind:eq value:"TAILNET" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorLogin | project |
Added | project |
NewServers | project |
OldServers | project |
Origin | project |
Removed | project |
TimeGenerated | project |