mirror of
https://github.com/traefik/traefik.git
synced 2026-06-18 19:38:23 +03:00
Fix basePath validation for dashboard template
This commit is contained in:
@@ -2,11 +2,11 @@ package dashboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
@@ -3,7 +3,7 @@ package static
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -57,6 +57,9 @@ const (
|
||||
DefaultUDPTimeout = 3 * time.Second
|
||||
)
|
||||
|
||||
// Allowed characters in URL following RFC 3986 (https://www.rfc-editor.org/rfc/rfc3986#section-2)
|
||||
var validBasePath = regexp.MustCompile(`^/[a-zA-Z0-9/_.-]*$`)
|
||||
|
||||
// Configuration is the static configuration.
|
||||
type Configuration struct {
|
||||
Global *Global `description:"Global configuration options" json:"global,omitempty" toml:"global,omitempty" yaml:"global,omitempty" export:"true"`
|
||||
@@ -464,8 +467,8 @@ func (c *Configuration) ValidateConfiguration() error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.API != nil && !path.IsAbs(c.API.BasePath) {
|
||||
return errors.New("API basePath must be a valid absolute path")
|
||||
if c.API != nil && !validBasePath.MatchString(c.API.BasePath) {
|
||||
return errors.New("API basePath must be a valid absolute URL path")
|
||||
}
|
||||
|
||||
if c.OCSP != nil {
|
||||
|
||||
@@ -282,3 +282,89 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateConfiguration_BasePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
basePath string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
desc: "valid simple path",
|
||||
basePath: "/api",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
desc: "valid path with segments",
|
||||
basePath: "/my/base/path",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
desc: "valid path with allowed special chars",
|
||||
basePath: "/valid/path-123",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
desc: "relative path",
|
||||
basePath: "api/path",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "XSS payload",
|
||||
basePath: `/api/"></script><script>alert("XSS")</script>`,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "path with spaces",
|
||||
basePath: "/path with spaces",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "path with angle brackets",
|
||||
basePath: "/path/<evil>",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "path with query string",
|
||||
basePath: "/api?foo=bar",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "path with fragment",
|
||||
basePath: "/api#section",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "valid root path",
|
||||
basePath: "/",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
desc: "path with quote",
|
||||
basePath: "/api/'onclick=alert(1)",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "path with encoded character",
|
||||
basePath: "/api%2Ftoto",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Configuration{
|
||||
API: &API{BasePath: test.basePath},
|
||||
}
|
||||
|
||||
err := cfg.ValidateConfiguration()
|
||||
if test.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user