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
+22 -6
View File
@@ -134,7 +134,7 @@ type Server struct {
publicKey key.NodePublic
logf logger.Logf
memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish)
meshKey string
meshKey key.DERPMesh
limitedLogf logger.Logf
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
dupPolicy dupPolicy
@@ -464,8 +464,13 @@ func genDroppedCounters() {
// amongst themselves.
//
// It must be called before serving begins.
func (s *Server) SetMeshKey(v string) {
s.meshKey = v
func (s *Server) SetMeshKey(v string) error {
k, err := key.ParseDERPMesh(v)
if err != nil {
return err
}
s.meshKey = k
return nil
}
// SetVerifyClients sets whether this DERP server verifies clients through tailscaled.
@@ -506,10 +511,10 @@ func (s *Server) SetTCPWriteTimeout(d time.Duration) {
}
// HasMeshKey reports whether the server is configured with a mesh key.
func (s *Server) HasMeshKey() bool { return s.meshKey != "" }
func (s *Server) HasMeshKey() bool { return !s.meshKey.IsZero() }
// MeshKey returns the configured mesh key, if any.
func (s *Server) MeshKey() string { return s.meshKey }
func (s *Server) MeshKey() key.DERPMesh { return s.meshKey }
// PrivateKey returns the server's private key.
func (s *Server) PrivateKey() key.NodePrivate { return s.privateKey }
@@ -1355,7 +1360,18 @@ func (c *sclient) requestMeshUpdate() {
// isMeshPeer reports whether the client is a trusted mesh peer
// node in the DERP region.
func (s *Server) isMeshPeer(info *clientInfo) bool {
return info != nil && info.MeshKey != "" && info.MeshKey == s.meshKey
// Compare mesh keys in constant time to prevent timing attacks.
// Since mesh keys are a fixed length, we dont need to be concerned
// about timing attacks on client mesh keys that are the wrong length.
// See https://github.com/tailscale/corp/issues/28720
if info == nil || info.MeshKey == "" {
return false
}
k, err := key.ParseDERPMesh(info.MeshKey)
if err != nil {
return false
}
return s.meshKey.Equal(k)
}
// verifyClient checks whether the client is allowed to connect to the derper,