Description: Insufficient inbound packet filtering in subnet routers and exit nodes
What happened?
In Tailscale versions earlier than 1.66.0, exit nodes, subnet
routers, and app connectors, could
allow inbound connections to other tailnet nodes from their local area network
(LAN). This vulnerability only affects Linux exit nodes, subnet routers, and
app connectors in tailnets where ACLs allow "src": "*"
, such as
with default ACLs.
Tailscale version 1.66.0 fixes the vulnerability. Additionally, a server-side
update changes the interpretation of "src": "*"
to mitigate the issue
specifically for exit nodes.
Special thanks to Hakan Ergan for reporting a similar
concern that led us to discover this vulnerability.
Who was affected?
This affected the following nodes using Tailscale version 1.65 or earlier:
- Exit nodes on Linux
- Subnet routers on Linux
- App connectors on Linux
- Regular nodes on all platforms connecting to the above nodes
Tailnets with custom ACLs that do not use "src": "*"
or any other value that
includes external IPs were not affected.
We are not aware of any active exploitation of this vulnerability.
What was the impact?
Devices outside of the tailnet, but on the same LAN as an exit node, subnet
router, or app connector could connect to ports on tailnet nodes that are
allowed by ACLs.
What do I need to do?
Upgrade the following nodes to 1.66.0 or later:
We recommend enabling auto-updates and updating all nodes to the latest
version, but it is not required to mitigate this vulnerability.
A server-side change mitigated this vulnerability for other types of affected
nodes.
Technical details
Below, we refer to exit nodes, subnet routers, and app connectors as
packet-forwarding nodes, because the details apply to all of them. Specific
types of packet-forwarding nodes are mentioned explicitly where their behavior
is different.
Before 1.66.0, packets between regular nodes and destination hosts behind
packet-forwarding nodes were filtered based on source/destination IP as
specified in tailnet ACLs. Specifying "src": "*"
in ACLs is equivalent to
"src": ["0.0.0.0/0", "::/0"]
, meaning any IP address. This allowed source IPs
outside of the tailnet to send packets to tailnet nodes via a packet-forwarding
node. This could be abused by malicious LAN hosts to connect into the tailnet
using a known tailnet node IP.
The attack only works on a LAN because:
- it relies on next-hop routing, which only works in a LAN
- destination IPs are in the subnet router's approved range, or in the CGNAT
range
100.64.0.0/10
, which are not routable over the Internet.
Attacks
Here are several attack scenarios.
Packet-forwarding node on an untrusted LAN.
A malicious host 10.0.0.1
on the same LAN as the packet-forwarding node
10.0.0.2
could craft packets with destination IP of a tailnet node
100.64.0.1
(using a command like ip route add 100.64.0.1/32 via 10.0.0.2 dev eth0
) and send them to the packet-forwarding node. The packet-forwarding node
would accept them and forward them to the victim node. The victim node would
see a packet from 10.0.0.1
and accept it if the tailnet ACLs allow this
source IP.
This scenario is very similar to a legitimate use-case of
site-to-site networking, where two subnets are bridged using
Tailscale subnet routers and the flag --snat-subnet-routes=false
.
Malicious shared exit node
A malicious exit node 100.64.0.4
from tailnet A could craft packets with
destination IP of tailnet B node 100.64.0.3
and any source IP other than
100.64.0.4
. Due to the built-in quarantining of shared
nodes, packets from 100.64.0.4
are rejected.
Mitigations
We implemented 3 separate mitigations for these attacks.
Redefine "src": "*"
in ACLs
While *
is a convenient shorthand in ACLs, Tailscale users almost never need
to allow connections from any IP. In most cases users intend *
as "all other
nodes in my tailnet". As a mitigation for this vulnerability, we redefined *
in src
section of ACLs to include:
- all tailnet nodes
- all IPs from approved subnet routes
The inclusion of IPs from approved subnet routes is needed for the site-to-site
networking setup.
For users that need the old semantics of *
we added a new
autogroup:danger-all
, which matches the old definition of *
.
Stateful packet filtering on packet-forwarding nodes
On Linux packet-forwarding nodes we added stateful packet filtering. This means
that these nodes keep track of forwarded connections and only allow return
packets for existing outbound connections. Inbound packets that don't belong to
an existing connection are dropped.
Because routing is implemented differently on non-Linux platforms, this
mitigation is only necessary on Linux.
Stateful filtering is enabled by default, except for existing subnet routers
that set --snat-subnet-routes=false
. You can disable stateful filtering using
tailscale up --stateful-filtering=false
.
Client-side quarantining of shared nodes
Quarantining of shared nodes was implemented by a packet
filter sent from the Tailscale control plane. This packet filter instructs
nodes to drop any inbound connections from the source IP of the shared node. To
prevent malicious shared exit nodes from crafting packets with different source
IPs, additional client-side quarantining logic was added. The 1.66.0 and later
clients reject all inbound connections from quarantined nodes, regardless of
their source IP. This is similar to how the "shields up" mode
works within the tailnet.