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>
andrew/debug-integration-tests
Percy Wegmann 7 months ago committed by Percy Wegmann
parent 3349e86c0a
commit 07e783c7be

@ -131,7 +131,11 @@ func TestSecretTokenAuth(t *testing.T) {
Transport: &http.Transport{DisableKeepAlives: true}, Transport: &http.Transport{DisableKeepAlives: true},
} }
addr := strings.Split(fileserverAddr, "|")[1] 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) resp, err := client.Get(u)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

@ -50,19 +50,28 @@ func NewFileServer() (*FileServer, error) {
} }
// } // }
tokenBytes := make([]byte, 32) secretToken, err := generateSecretToken()
_, err = rand.Read(tokenBytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("generate token bytes: %w", err) return nil, err
} }
return &FileServer{ return &FileServer{
l: l, l: l,
secretToken: hex.EncodeToString(tokenBytes), secretToken: secretToken,
shareHandlers: make(map[string]http.Handler), shareHandlers: make(map[string]http.Handler),
}, nil }, 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 // Addr returns the address at which this FileServer is listening. This
// includes the secret token in front of the address, delimited by a pipe |. // includes the secret token in front of the address, delimited by a pipe |.
func (s *FileServer) Addr() string { 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 // ServeHTTP implements the http.Handler interface. This requires a secret
// Mark-of-the-Web bypass attacks if someone visits this fileserver directly // token in the path in order to prevent Mark-of-the-Web (MOTW) bypass attacks
// within a browser, it requires that the first path element be this file // of the below sort:
// server's secret token. Without knowing the secret token, it's impossible //
// to construct a URL that passes validation. // 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) { func (s *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
parts := shared.CleanAndSplit(r.URL.Path) parts := shared.CleanAndSplit(r.URL.Path)

Loading…
Cancel
Save