mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 23:07:44 +00:00
client/web: keep redirects on-site (#10525)
Ensure we don't create Location: header URLs that have leading //, which is a schema-less reference to arbitrary 3rd-party sites. That is, //example.com/foo redirects off-site, while /example.com/foo is an on-site path URL. Fixes tailscale/corp#16268 Signed-off-by: Chris Palmer <cpalmer@tailscale.com>
This commit is contained in:
parent
727acf96a6
commit
b62a3fc895
@ -15,6 +15,7 @@
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
@ -174,6 +175,13 @@ func NewServer(opts ServerOpts) (s *Server, err error) {
|
||||
newAuthURL: opts.NewAuthURL,
|
||||
waitAuthURL: opts.WaitAuthURL,
|
||||
}
|
||||
if opts.PathPrefix != "" {
|
||||
// In enforcePrefix, we add the necessary leading '/'. If we did not
|
||||
// strip 1 or more leading '/'s here, we would end up redirecting
|
||||
// clients to e.g. //example.com (a schema-less URL that points to
|
||||
// another site). See https://github.com/tailscale/corp/issues/16268.
|
||||
s.pathPrefix = strings.TrimLeft(path.Clean(opts.PathPrefix), "/\\")
|
||||
}
|
||||
if s.mode == ManageServerMode {
|
||||
if opts.NewAuthURL == nil {
|
||||
return nil, fmt.Errorf("must provide a NewAuthURL implementation")
|
||||
|
@ -939,6 +939,49 @@ func(metricName string) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoOffSiteRedirect(t *testing.T) {
|
||||
options := ServerOpts{
|
||||
Mode: LoginServerMode,
|
||||
// Emulate the admin using a --prefix option with leading slashes:
|
||||
PathPrefix: "//evil.example.com/goat",
|
||||
CGIMode: true,
|
||||
}
|
||||
s, err := NewServer(options)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
target string
|
||||
wantHandled bool
|
||||
wantLocation string
|
||||
}{
|
||||
{
|
||||
name: "2-slashes",
|
||||
target: "http://localhost//evil.example.com/goat",
|
||||
// We must also get the trailing slash added:
|
||||
wantLocation: "/evil.example.com/goat/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s.logf = t.Logf
|
||||
r := httptest.NewRequest(httpm.GET, tt.target, nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
location := w.Header().Get("Location")
|
||||
if location != tt.wantLocation {
|
||||
t.Errorf("request(%q) got wrong location; want=%q, got=%q", tt.target, tt.wantLocation, location)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireTailscaleIP(t *testing.T) {
|
||||
self := &ipnstate.PeerStatus{
|
||||
TailscaleIPs: []netip.Addr{
|
||||
@ -1007,7 +1050,7 @@ func TestRequireTailscaleIP(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.target, func(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s.logf = t.Logf
|
||||
r := httptest.NewRequest(httpm.GET, tt.target, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
Loading…
x
Reference in New Issue
Block a user