Przeglądaj źródła

fix(sanitizer): reject non-web schemes in iframe source URLs

The iframe allowlist was checked against the URL host alone, which
url.Parse populates regardless of scheme. A source such as
javascript://youtube.com/%0Aalert(document.domain) therefore matched an
allowlisted domain and was rendered as-is.

Parse the source URL and only accept http, https, and protocol-relative
URLs before looking up the domain. Comparisons keep using the host with
its port so that INVIDIOUS_INSTANCE values that include one still match.
Fred 1 miesiąc temu
rodzic
commit
3b5a7ee47b

+ 18 - 1
internal/reader/sanitizer/sanitizer.go

@@ -201,7 +201,24 @@ func SanitizeHTML(baseURL, rawHTML string, sanitizerOptions *SanitizerOptions) s
 }
 
 func findAllowedIframeSourceDomain(iframeSourceURL string) (string, bool) {
-	iframeSourceDomain := urllib.DomainWithoutWWW(iframeSourceURL)
+	parsedURL, err := url.Parse(iframeSourceURL)
+	if err != nil {
+		return "", false
+	}
+
+	switch parsedURL.Scheme {
+	case "http", "https":
+		// Only web URLs are allowed for embedded content.
+	case "":
+		// Preserve support for protocol-relative iframe URLs.
+	default:
+		return "", false
+	}
+
+	iframeSourceDomain := strings.TrimPrefix(parsedURL.Host, "www.")
+	if iframeSourceDomain == "" {
+		return "", false
+	}
 
 	if _, ok := iframeAllowList[iframeSourceDomain]; ok {
 		return iframeSourceDomain, true

+ 40 - 0
internal/reader/sanitizer/sanitizer_test.go

@@ -434,6 +434,24 @@ func TestInvidiousIFrame(t *testing.T) {
 	}
 }
 
+func TestInvidiousIFrameWithPort(t *testing.T) {
+	os.Setenv("INVIDIOUS_INSTANCE", "invidious.example.com:3000")
+
+	defer os.Clearenv()
+	var err error
+	if config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables(); err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	input := `<iframe src="https://invidious.example.com:3000/embed/1234"></iframe>`
+	expected := `<iframe src="https://invidious.example.com:3000/embed/1234" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
+	output := sanitizeHTMLWithDefaultOptions("http://example.com/", input)
+
+	if expected != output {
+		t.Errorf(`Wrong output: %q != %q`, expected, output)
+	}
+}
+
 func TestIFrameAllowList(t *testing.T) {
 	config.Opts = config.NewConfigOptions()
 
@@ -465,6 +483,28 @@ func TestIFrameAllowList(t *testing.T) {
 	}
 }
 
+func TestIFrameRejectsNonWebSchemes(t *testing.T) {
+	config.Opts = config.NewConfigOptions()
+
+	testCases := []string{
+		`javascript://youtube.com/%0Aalert(document.domain)`,
+		`JAVASCRIPT://youtube.com/%0Aalert(document.domain)`,
+		`data://youtube.com/text/html,<script>alert(1)</script>`,
+		`vbscript://youtube.com/alert(1)`,
+		`ftp://youtube.com/embed/test`,
+	}
+
+	for _, iframeURL := range testCases {
+		t.Run(iframeURL, func(t *testing.T) {
+			input := fmt.Sprintf(`<iframe src="%s"></iframe>`, iframeURL)
+			output := sanitizeHTMLWithDefaultOptions("https://example.com/", input)
+			if output != "" {
+				t.Errorf("Unsafe iframe was preserved: %q", output)
+			}
+		})
+	}
+}
+
 func TestCustomYoutubeEmbedURL(t *testing.T) {
 	os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://www.invidious.custom/embed/")