Sanitize request path
This commit is contained in:
parent
299a16f0a4
commit
dd5cb68cb1
12 changed files with 278 additions and 17 deletions
|
@ -52,6 +52,7 @@ func (ep *EntryPoint) SetDefaults() {
|
|||
ep.ForwardedHeaders = &ForwardedHeaders{}
|
||||
ep.UDP = &UDPConfig{}
|
||||
ep.UDP.SetDefaults()
|
||||
ep.HTTP.SetDefaults()
|
||||
ep.HTTP2 = &HTTP2Config{}
|
||||
ep.HTTP2.SetDefaults()
|
||||
}
|
||||
|
@ -61,7 +62,14 @@ type HTTPConfig struct {
|
|||
Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,omitempty" export:"true"`
|
||||
Middlewares []string `description:"Default middlewares for the routers linked to the entry point." json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty"`
|
||||
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty" export:"true"`
|
||||
SanitizePath *bool `description:"Defines whether to enable request path sanitization (removal of /./, /../ and multiple slash sequences)." json:"sanitizePath,omitempty" toml:"sanitizePath,omitempty" yaml:"sanitizePath,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (h *HTTPConfig) SetDefaults() {
|
||||
sanitizePath := true
|
||||
h.SanitizePath = &sanitizePath
|
||||
}
|
||||
|
||||
// HTTP2Config is the HTTP2 configuration of an entry point.
|
||||
|
|
|
@ -571,7 +571,12 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
return nil, err
|
||||
}
|
||||
|
||||
handler = denyFragment(handler)
|
||||
if configuration.HTTP.SanitizePath != nil && *configuration.HTTP.SanitizePath {
|
||||
// sanitizePath is used to clean the URL path by removing /../, /./ and duplicate slash sequences,
|
||||
// to make sure the path is interpreted by the backends as it is evaluated inside rule matchers.
|
||||
handler = sanitizePath(handler)
|
||||
}
|
||||
|
||||
if configuration.HTTP.EncodeQuerySemicolons {
|
||||
handler = encodeQuerySemicolons(handler)
|
||||
} else {
|
||||
|
@ -589,6 +594,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
})
|
||||
}
|
||||
|
||||
handler = denyFragment(handler)
|
||||
|
||||
serverHTTP := &http.Server{
|
||||
Handler: handler,
|
||||
ErrorLog: httpServerLogger,
|
||||
|
@ -713,3 +720,20 @@ func denyFragment(h http.Handler) http.Handler {
|
|||
h.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// sanitizePath removes the "..", "." and duplicate slash segments from the URL.
|
||||
// It cleans the request URL Path and RawPath, and updates the request URI.
|
||||
func sanitizePath(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
r2 := new(http.Request)
|
||||
*r2 = *req
|
||||
|
||||
// Cleans the URL raw path and path.
|
||||
r2.URL = r2.URL.JoinPath()
|
||||
|
||||
// Because the reverse proxy director is building query params from requestURI it needs to be updated as well.
|
||||
r2.RequestURI = r2.URL.RequestURI()
|
||||
|
||||
h.ServeHTTP(rw, r2)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -382,3 +383,44 @@ func TestKeepAliveH2c(t *testing.T) {
|
|||
// to change.
|
||||
require.Contains(t, err.Error(), "use of closed network connection")
|
||||
}
|
||||
|
||||
func TestSanitizePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{path: "/b", expected: "/b"},
|
||||
{path: "/b/", expected: "/b/"},
|
||||
{path: "/../../b/", expected: "/b/"},
|
||||
{path: "/../../b", expected: "/b"},
|
||||
{path: "/a/b/..", expected: "/a"},
|
||||
{path: "/a/b/../", expected: "/a/"},
|
||||
{path: "/a/../../b", expected: "/b"},
|
||||
{path: "/..///b///", expected: "/b/"},
|
||||
{path: "/a/../b", expected: "/b"},
|
||||
{path: "/a/./b", expected: "/a/b"},
|
||||
{path: "/a//b", expected: "/a/b"},
|
||||
{path: "/a/../../b", expected: "/b"},
|
||||
{path: "/a/../c/../b", expected: "/b"},
|
||||
{path: "/a/../../../c/../b", expected: "/b"},
|
||||
{path: "/a/../c/../../b", expected: "/b"},
|
||||
{path: "/a/..//c/.././b", expected: "/b"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("Testing case: "+test.path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var callCount int
|
||||
clean := sanitizePath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
assert.Equal(t, test.expected, r.URL.Path)
|
||||
}))
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo"+test.path, http.NoBody)
|
||||
clean.ServeHTTP(httptest.NewRecorder(), request)
|
||||
|
||||
assert.Equal(t, 1, callCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue