diff --git a/integration/simple_test.go b/integration/simple_test.go index baf7aa230..f88afb9e4 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1480,3 +1480,31 @@ func (s *SimpleSuite) TestEncodeSemicolons(c *check.C) { } } } + +func (s *SimpleSuite) TestDenyFragment(c *check.C) { + s.createComposeProject(c, "base") + + s.composeUp(c) + defer s.composeDown(c) + + cmd, output := s.traefikCmd(withConfigFile("fixtures/simple_default.toml")) + defer output(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + // Expected a 404 as we did not configure anything + err = try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + conn, err := net.Dial("tcp", "127.0.0.1:8000") + c.Assert(err, checker.IsNil) + + _, err = conn.Write([]byte("GET /#/?bar=toto;boo=titi HTTP/1.1\nHost: other.localhost\n\n")) + c.Assert(err, checker.IsNil) + + resp, err := http.ReadResponse(bufio.NewReader(conn), nil) + c.Assert(err, checker.IsNil) + c.Assert(resp.StatusCode, checker.Equals, http.StatusBadRequest) +} diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 96cc29dc4..a3f389605 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -535,6 +535,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati return nil, err } + handler = denyFragment(handler) if configuration.HTTP.EncodeQuerySemicolons { handler = encodeQuerySemicolons(handler) } else { @@ -620,3 +621,20 @@ func encodeQuerySemicolons(h http.Handler) http.Handler { } }) } + +// When go receives an HTTP request, it assumes the absence of fragment URL. +// However, it is still possible to send a fragment in the request. +// In this case, Traefik will encode the '#' character, altering the request's intended meaning. +// To avoid this behavior, the following function rejects requests that include a fragment in the URL. +func denyFragment(h http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if strings.Contains(req.URL.RawPath, "#") { + log.WithoutContext().Debugf("Rejecting request because it contains a fragment in the URL path: %s", req.URL.RawPath) + rw.WriteHeader(http.StatusBadRequest) + + return + } + + h.ServeHTTP(rw, req) + }) +}