cmd/derper: fix mesh auth for DERP servers (#16061)

To authenticate mesh keys, the DERP servers used a simple == comparison,
which is susceptible to a side channel timing attack.

By extracting the mesh key for a DERP server, an attacker could DoS it
by forcing disconnects using derp.Client.ClosePeer. They could also
enumerate the public Wireguard keys, IP addresses and ports for nodes
connected to that DERP server.

DERP servers configured without mesh keys deny all such requests.

This patch also extracts the mesh key logic into key.DERPMesh, to
prevent this from happening again.

Security bulletin: https://tailscale.com/security-bulletins#ts-2025-003

Fixes tailscale/corp#28720

Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
Simon Law
2025-05-22 12:14:16 -07:00
committed by GitHub
parent aa8bc23c49
commit 3ee4c60ff0
9 changed files with 338 additions and 71 deletions

View File

@@ -30,7 +30,7 @@ type Client struct {
logf logger.Logf
nc Conn
br *bufio.Reader
meshKey string
meshKey key.DERPMesh
canAckPings bool
isProber bool
@@ -56,7 +56,7 @@ func (f clientOptFunc) update(o *clientOpt) { f(o) }
// clientOpt are the options passed to newClient.
type clientOpt struct {
MeshKey string
MeshKey key.DERPMesh
ServerPub key.NodePublic
CanAckPings bool
IsProber bool
@@ -66,7 +66,7 @@ type clientOpt struct {
// access to join the mesh.
//
// An empty key means to not use a mesh key.
func MeshKey(key string) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.MeshKey = key }) }
func MeshKey(k key.DERPMesh) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.MeshKey = k }) }
// IsProber returns a ClientOpt to pass to the DERP server during connect to
// declare that this client is a a prober.
@@ -182,7 +182,7 @@ type clientInfo struct {
func (c *Client) sendClientKey() error {
msg, err := json.Marshal(clientInfo{
Version: ProtocolVersion,
MeshKey: c.meshKey,
MeshKey: c.meshKey.String(),
CanAckPings: c.canAckPings,
IsProber: c.isProber,
})