Detection rules › Kusto
Tailscale: Split-DNS configuration modified
Identifies when the tailnet split-DNS configuration is modified. Split-DNS overrides per-domain resolution within the tailnet - an attacker adding a new domain mapping or changing the resolver IP can hijack DNS for that domain.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access | |
| Command & Control |
Rule body
id: b4c5d6e7-1234-5678-90ab-cdef12345010
name: "Tailscale: Split-DNS configuration modified"
description: |
Identifies when the tailnet split-DNS configuration is modified. Split-DNS overrides per-domain resolution within the tailnet - an attacker adding a new domain mapping or changing the resolver IP can hijack DNS for that domain.
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_SPLIT_DNS"
| extend ActorLogin = tostring(Actor.loginName)
| extend OldDomains = bag_keys(Old)
| extend NewDomains = bag_keys(New)
| extend AddedDomains = set_difference(NewDomains, OldDomains)
| extend RemovedDomains = set_difference(OldDomains, NewDomains)
| project TimeGenerated, ActorLogin, AddedDomains, RemovedDomains, Old, New, 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_SPLIT_DNS"
Stage 5: extend (5 consecutive steps)
| extend ActorLogin = tostring(Actor.loginName)
| extend OldDomains = bag_keys(Old)
| extend NewDomains = bag_keys(New)
| extend AddedDomains = set_difference(NewDomains, OldDomains)
| extend RemovedDomains = set_difference(OldDomains, NewDomains)
Stage 6: project
| project TimeGenerated, ActorLogin, AddedDomains, RemovedDomains, Old, New, 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_SPLIT_DNS" |
type | eq |
| field:"type" kind:eq value:"TAILNET" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorLogin | project |
AddedDomains | project |
New | project |
Old | project |
Origin | project |
RemovedDomains | project |
TimeGenerated | project |