CVE-2020-15129 (published 2020-07-30T16:15Z)

In Traefik before versions 1.7.26, 2.2.8, and 2.3.0-rc3, there exists a potential open redirect vulnerability in Traefik\'s handling of the "X-Forwarded-Prefix" header. The Traefik API dashboard component doesn\'t validate that the value of the header "X-Forwarded-Prefix" is a site relative path and will redirect to any header provided URI. Successful exploitation of an open redirect can be used to entice victims to disclose sensitive information. Active Exploitation of this issue is unlikely as it would require active header injection, however the Traefik team addressed this issue nonetheless to prevent abuse in e.g. cache poisoning scenarios.

Severity: MEDIUM
CVSS v2 base score: 4.0 (AV:N/AC:H/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 4.7, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/containous/traefik
commit_details: repo_url: https://github.com/containous/traefik
author: Ludovic Fernandez
author_date: 2020-07-28 10:08:03+02:00
msg: fix: clean X-Forwarded-Prefix header for the dashboard.
repository: repo_url: https://github.com/containous/traefik
repo_name: traefik/traefik
description: The Cloud Native Application Proxy
repo_language: Go
owner: containous
file_changes: filename: dashboard.go
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@ package api
 
 import (
 	"net/http"
+	"net/url"
 
 	"github.com/containous/traefik/v2/pkg/log"
 	assetfs "github.com/elazarl/go-bindata-assetfs"
@@ -23,11 +24,29 @@ func (g DashboardHandler) Append(router *mux.Router) {
 	// Expose dashboard
 	router.Methods(http.MethodGet).
 		Path("/").
-		HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
-			http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", http.StatusFound)
+		HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+			http.Redirect(resp, req, safePrefix(req)+"/dashboard/", http.StatusFound)
 		})
 
 	router.Methods(http.MethodGet).
 		PathPrefix("/dashboard/").
 		Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets)))
 }
+
+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
+}

programming_language: Go
filename: dashboard_test.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,54 @@
+package api
+
+import (
+	"net/http"
+	"testing"
+
+	"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 {
+		test := test
+		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)
+		})
+	}
+}

programming_language: Go