Przeglądaj źródła

fix(urllib): reject backslashes in relative path validation

Browsers normalize backslashes to forward slashes, so a redirect target
like "/\evil.com" parsed as a relative path by url.Parse and resolved to
//evil.com by the browser, resulting in an open redirect. Reject any link
containing a backslash before validating it as a relative path.
Frédéric Guillot 14 godzin temu
rodzic
commit
c896bafdaa

+ 3 - 0
internal/http/response/html_test.go

@@ -224,6 +224,9 @@ func TestHTMLRedirectRejectsUnsafeTargets(t *testing.T) {
 		"file:///etc/passwd",
 		"mailto:victim@example.org",
 		"//evil.example.org/path",
+		`/\evil.example.org/path`,
+		`\evil.example.org\path`,
+		`/foo\bar`,
 		"ftp://example.org/file",
 		"",
 	}

+ 9 - 0
internal/urllib/url.go

@@ -19,6 +19,15 @@ func IsRelativePath(link string) bool {
 	if link == "" {
 		return false
 	}
+
+	// Reject backslashes: Go's url.Parse treats them as ordinary path
+	// characters, but browsers normalize them to forward slashes, so a target
+	// like "/\evil.com" would parse as relative here yet redirect to
+	// //evil.com in the browser (open redirect).
+	if strings.Contains(link, "\\") {
+		return false
+	}
+
 	if parsedURL, err := url.Parse(link); err == nil {
 		// Only allow relative paths (not scheme-relative URLs like //example.org)
 		// and ensure the URL doesn't have a host component

+ 3 - 0
internal/urllib/url_test.go

@@ -28,6 +28,9 @@ func TestIsRelativePath(t *testing.T) {
 		"http://example.org/file.ext":  false,
 		"//example.org/file.ext":       false,
 		"//example.org":                false,
+		`/\example.org`:                false,
+		`\example.org`:                 false,
+		`path\to\file.ext`:             false,
 		"ftp://example.org/file.ext":   false,
 		"mailto:user@example.org":      false,
 		"magnet:?xt=urn:btih:example":  false,