drive: use secret token to authenticate access to file server on localhost

This prevents Mark-of-the-Web bypass attacks in case someone visits the
localhost WebDAV server directly.

Fixes tailscale/corp#19592

Signed-off-by: Percy Wegmann <percy@tailscale.com>
pull/11956/head
Percy Wegmann 1 month ago
parent 05c01f1e23
commit b839a1bb6c
No known key found for this signature in database
GPG Key ID: 29D8CDEB4C13D48B

@ -131,7 +131,11 @@ func TestSecretTokenAuth(t *testing.T) {
Transport: &http.Transport{DisableKeepAlives: true},
}
addr := strings.Split(fileserverAddr, "|")[1]
u := fmt.Sprintf("http://%s/%s/%s", addr, "fakesecret", url.PathEscape(file111))
wrongSecret, err := generateSecretToken()
if err != nil {
t.Fatal(err)
}
u := fmt.Sprintf("http://%s/%s/%s", addr, wrongSecret, url.PathEscape(file111))
resp, err := client.Get(u)
if err != nil {
t.Fatal(err)

@ -50,19 +50,28 @@ func NewFileServer() (*FileServer, error) {
}
// }
tokenBytes := make([]byte, 32)
_, err = rand.Read(tokenBytes)
secretToken, err := generateSecretToken()
if err != nil {
return nil, fmt.Errorf("generate token bytes: %w", err)
return nil, err
}
return &FileServer{
l: l,
secretToken: hex.EncodeToString(tokenBytes),
secretToken: secretToken,
shareHandlers: make(map[string]http.Handler),
}, nil
}
// generateSecretToken generates a hex-encoded 256 bit secet.
func generateSecretToken() (string, error) {
tokenBytes := make([]byte, 32)
_, err := rand.Read(tokenBytes)
if err != nil {
return "", fmt.Errorf("generateSecretToken: %w", err)
}
return hex.EncodeToString(tokenBytes), nil
}
// Addr returns the address at which this FileServer is listening. This
// includes the secret token in front of the address, delimited by a pipe |.
func (s *FileServer) Addr() string {
@ -109,11 +118,21 @@ func (s *FileServer) SetShares(shares map[string]string) {
}
}
// ServeHTTP implements the http.Handler interface. In order to prevent
// Mark-of-the-Web bypass attacks if someone visits this fileserver directly
// within a browser, it requires that the first path element be this file
// server's secret token. Without knowing the secret token, it's impossible
// to construct a URL that passes validation.
// ServeHTTP implements the http.Handler interface. This requires a secret
// token in the path in order to prevent Mark-of-the-Web (MOTW) bypass attacks
// of the below sort:
//
// 1. Attacker with write access to the share puts a malicious file via
// http://100.100.100.100:8080/<tailnet>/<machine>/</share>/bad.exe
// 2. Attacker then induces victim to visit
// http://localhost:[PORT]/<share>/bad.exe
// 3. Because that is loaded from localhost, it does not get the MOTW
// thereby bypasses some OS-level security.
//
// The path on this file server is actually not as above, but rather
// http://localhost:[PORT]/<secretToken>/<share>/bad.exe. Unless the attacker
// can discover the secretToken, the attacker cannot craft a localhost URL that
// will work.
func (s *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
parts := shared.CleanAndSplit(r.URL.Path)

Loading…
Cancel
Save