Drop untrusted X-Forwarded-Prefix header

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Romain 2024-11-08 12:12:35 +01:00 committed by GitHub
parent 6f18344c56
commit 2096fd7081
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 31 additions and 65 deletions

View file

@ -3,7 +3,7 @@ package dashboard
import ( import (
"io/fs" "io/fs"
"net/http" "net/http"
"net/url" "strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/traefik/traefik/v2/webui" "github.com/traefik/traefik/v2/webui"
@ -25,7 +25,8 @@ func Append(router *mux.Router, customAssets fs.FS) {
router.Methods(http.MethodGet). router.Methods(http.MethodGet).
Path("/"). Path("/").
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
http.Redirect(resp, req, safePrefix(req)+"/dashboard/", http.StatusFound) prefix := strings.TrimSuffix(req.Header.Get("X-Forwarded-Prefix"), "/")
http.Redirect(resp, req, prefix+"/dashboard/", http.StatusFound)
}) })
router.Methods(http.MethodGet). router.Methods(http.MethodGet).
@ -48,21 +49,3 @@ func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;") w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
http.FileServerFS(assets).ServeHTTP(w, r) http.FileServerFS(assets).ServeHTTP(w, r)
} }
func safePrefix(req *http.Request) string {
prefix := req.Header.Get("X-Forwarded-Prefix")
if prefix == "" {
return ""
}
parse, err := url.Parse(prefix)
if err != nil {
return ""
}
if parse.Host != "" {
return ""
}
return parse.Path
}

View file

@ -10,53 +10,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_safePrefix(t *testing.T) {
testCases := []struct {
desc string
value string
expected string
}{
{
desc: "host",
value: "https://example.com",
expected: "",
},
{
desc: "host with path",
value: "https://example.com/foo/bar?test",
expected: "",
},
{
desc: "path",
value: "/foo/bar",
expected: "/foo/bar",
},
{
desc: "path without leading slash",
value: "foo/bar",
expected: "foo/bar",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
req, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
require.NoError(t, err)
req.Header.Set("X-Forwarded-Prefix", test.value)
prefix := safePrefix(req)
assert.Equal(t, test.expected, prefix)
})
}
}
func Test_ContentSecurityPolicy(t *testing.T) { func Test_ContentSecurityPolicy(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string

View file

@ -20,6 +20,7 @@ const (
xForwardedServer = "X-Forwarded-Server" xForwardedServer = "X-Forwarded-Server"
xForwardedURI = "X-Forwarded-Uri" xForwardedURI = "X-Forwarded-Uri"
xForwardedMethod = "X-Forwarded-Method" xForwardedMethod = "X-Forwarded-Method"
xForwardedPrefix = "X-Forwarded-Prefix"
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert" xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info" xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
xRealIP = "X-Real-Ip" xRealIP = "X-Real-Ip"
@ -35,6 +36,7 @@ var xHeaders = []string{
xForwardedServer, xForwardedServer,
xForwardedURI, xForwardedURI,
xForwardedMethod, xForwardedMethod,
xForwardedPrefix,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xRealIP, xRealIP,

View file

@ -48,6 +48,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"}, xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"}, xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"}, xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
xForwardedFor: "10.0.1.0, 10.0.1.12", xForwardedFor: "10.0.1.0, 10.0.1.12",
@ -55,6 +56,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "GET", xForwardedMethod: "GET",
xForwardedTLSClientCert: "Cert", xForwardedTLSClientCert: "Cert",
xForwardedTLSClientCertInfo: "CertInfo", xForwardedTLSClientCertInfo: "CertInfo",
xForwardedPrefix: "/prefix",
}, },
}, },
{ {
@ -68,6 +70,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"}, xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"}, xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"}, xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
xForwardedFor: "", xForwardedFor: "",
@ -75,6 +78,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "", xForwardedMethod: "",
xForwardedTLSClientCert: "", xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "", xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
}, },
}, },
{ {
@ -88,6 +92,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"}, xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"}, xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"}, xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
xForwardedFor: "10.0.1.0, 10.0.1.12", xForwardedFor: "10.0.1.0, 10.0.1.12",
@ -95,6 +100,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "GET", xForwardedMethod: "GET",
xForwardedTLSClientCert: "Cert", xForwardedTLSClientCert: "Cert",
xForwardedTLSClientCertInfo: "CertInfo", xForwardedTLSClientCertInfo: "CertInfo",
xForwardedPrefix: "/prefix",
}, },
}, },
{ {
@ -108,6 +114,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"}, xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"}, xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"}, xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
xForwardedFor: "", xForwardedFor: "",
@ -115,6 +122,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "", xForwardedMethod: "",
xForwardedTLSClientCert: "", xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "", xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
}, },
}, },
{ {
@ -128,6 +136,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"}, xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"}, xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"}, xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
xForwardedFor: "10.0.1.0, 10.0.1.12", xForwardedFor: "10.0.1.0, 10.0.1.12",
@ -135,6 +144,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "GET", xForwardedMethod: "GET",
xForwardedTLSClientCert: "Cert", xForwardedTLSClientCert: "Cert",
xForwardedTLSClientCertInfo: "CertInfo", xForwardedTLSClientCertInfo: "CertInfo",
xForwardedPrefix: "/prefix",
}, },
}, },
{ {
@ -148,6 +158,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: {"GET"}, xForwardedMethod: {"GET"},
xForwardedTLSClientCert: {"Cert"}, xForwardedTLSClientCert: {"Cert"},
xForwardedTLSClientCertInfo: {"CertInfo"}, xForwardedTLSClientCertInfo: {"CertInfo"},
xForwardedPrefix: {"/prefix"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
xForwardedFor: "", xForwardedFor: "",
@ -155,6 +166,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedMethod: "", xForwardedMethod: "",
xForwardedTLSClientCert: "", xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "", xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
}, },
}, },
{ {
@ -283,6 +295,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort, xForwardedPort,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP, xRealIP,
}, },
xForwardedProto: {"foo"}, xForwardedProto: {"foo"},
@ -293,6 +306,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"}, xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"}, xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"}, xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"}, xRealIP: {"foo"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
@ -304,6 +318,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "80", xForwardedPort: "80",
xForwardedTLSClientCert: "", xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "", xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
xRealIP: "", xRealIP: "",
connection: "", connection: "",
}, },
@ -321,6 +336,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort, xForwardedPort,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP, xRealIP,
}, },
xForwardedProto: {"foo"}, xForwardedProto: {"foo"},
@ -331,6 +347,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"}, xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"}, xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"}, xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"}, xRealIP: {"foo"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
@ -342,6 +359,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "foo", xForwardedPort: "foo",
xForwardedTLSClientCert: "foo", xForwardedTLSClientCert: "foo",
xForwardedTLSClientCertInfo: "foo", xForwardedTLSClientCertInfo: "foo",
xForwardedPrefix: "foo",
xRealIP: "foo", xRealIP: "foo",
connection: "", connection: "",
}, },
@ -358,6 +376,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort, xForwardedPort,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP, xRealIP,
}, },
incomingHeaders: map[string][]string{ incomingHeaders: map[string][]string{
@ -370,6 +389,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort, xForwardedPort,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP, xRealIP,
}, },
xForwardedProto: {"foo"}, xForwardedProto: {"foo"},
@ -380,6 +400,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"}, xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"}, xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"}, xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"}, xRealIP: {"foo"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
@ -391,6 +412,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "80", xForwardedPort: "80",
xForwardedTLSClientCert: "", xForwardedTLSClientCert: "",
xForwardedTLSClientCertInfo: "", xForwardedTLSClientCertInfo: "",
xForwardedPrefix: "",
xRealIP: "", xRealIP: "",
connection: "", connection: "",
}, },
@ -407,6 +429,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort, xForwardedPort,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP, xRealIP,
}, },
incomingHeaders: map[string][]string{ incomingHeaders: map[string][]string{
@ -419,6 +442,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort, xForwardedPort,
xForwardedTLSClientCert, xForwardedTLSClientCert,
xForwardedTLSClientCertInfo, xForwardedTLSClientCertInfo,
xForwardedPrefix,
xRealIP, xRealIP,
}, },
xForwardedProto: {"foo"}, xForwardedProto: {"foo"},
@ -429,6 +453,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: {"foo"}, xForwardedPort: {"foo"},
xForwardedTLSClientCert: {"foo"}, xForwardedTLSClientCert: {"foo"},
xForwardedTLSClientCertInfo: {"foo"}, xForwardedTLSClientCertInfo: {"foo"},
xForwardedPrefix: {"foo"},
xRealIP: {"foo"}, xRealIP: {"foo"},
}, },
expectedHeaders: map[string]string{ expectedHeaders: map[string]string{
@ -440,6 +465,7 @@ func TestServeHTTP(t *testing.T) {
xForwardedPort: "foo", xForwardedPort: "foo",
xForwardedTLSClientCert: "foo", xForwardedTLSClientCert: "foo",
xForwardedTLSClientCertInfo: "foo", xForwardedTLSClientCertInfo: "foo",
xForwardedPrefix: "foo",
xRealIP: "foo", xRealIP: "foo",
connection: "", connection: "",
}, },