diff --git a/app/Models/SimplePieCustom.php b/app/Models/SimplePieCustom.php index b2ab28e3b..cfa83c853 100644 --- a/app/Models/SimplePieCustom.php +++ b/app/Models/SimplePieCustom.php @@ -64,7 +64,8 @@ final class FreshRSS_SimplePieCustom extends \SimplePie\SimplePie $this->rename_attributes(['id', 'class']); $this->allow_aria_attr(true); $this->allow_data_attr(true); - $this->disallow_uri_schemes(['javascript']); + // `data:` is still allowed for images (see Sanitize::is_allowed_uri()), but blocked on navigational/framing attributes + $this->disallow_uri_schemes(['javascript', 'data']); $this->allowed_html_attributes([ // HTML 'dir', diff --git a/lib/composer.json b/lib/composer.json index eb2f0478e..0807b5098 100644 --- a/lib/composer.json +++ b/lib/composer.json @@ -18,7 +18,7 @@ "marienfressinaud/lib_opml": "dev-main#f0e850b6394af90b898daf0e65fcc7363457b844", "phpgt/cssxpath": "v1.6.0", "phpmailer/phpmailer": "7.1.1", - "simplepie/simplepie": "dev-freshrss#caa6737811d75301720d07b30d9b53a4ab0b969d" + "simplepie/simplepie": "dev-freshrss#fcfbf7769eb9b3725a1f07b609771b0d7a12b0c7" }, "config": { "sort-packages": true, diff --git a/lib/simplepie/simplepie/src/Sanitize.php b/lib/simplepie/simplepie/src/Sanitize.php index 819071391..9a5aca0cd 100644 --- a/lib/simplepie/simplepie/src/Sanitize.php +++ b/lib/simplepie/simplepie/src/Sanitize.php @@ -677,7 +677,7 @@ class Sanitize implements RegistryAware $value = $this->registry->call(Misc::class, 'absolutize_url', [$element->getAttribute($attribute), $this->base]); if ($value !== false) { // Block disallowed URI protocols (e.g. javascript:), otherwise force HTTPS where applicable - $value = ($this->disallowed_uri_schemes !== [] && !$this->is_allowed_scheme($value)) + $value = ($this->disallowed_uri_schemes !== [] && !$this->is_allowed_uri($tag, $attribute, $value)) ? 'unsafe:' . $value : $this->https_url($value); $element->setAttribute($attribute, $value); @@ -779,6 +779,36 @@ class Sanitize implements RegistryAware return !in_array($scheme, $this->disallowed_uri_schemes, true); } + /** + * Elements/attributes on which a `data:` URI carrying an image is still allowed + * even when the `data` scheme is otherwise disallowed. Such URIs are inert + * (an `` never executes script), whereas `data:` on navigational or framing + * attributes (``, `', 'src="data:']; + yield 'data:text/html URL in image' => ['', 'src="data:']; + yield 'data:image URL in link' => ['click', 'href="data:']; } public static function test_sanitizeHTML_whenSafeHtml_keepsAllowedTags(): void { @@ -92,4 +96,19 @@ final class SimplePieCustomTest extends \PHPUnit\Framework\TestCase { yield 'double unclosed tag' => [' x', 10, 'x']; yield 'triple unclosed tag' => [' y', 20, 'y']; } + + /** + * Inline `data:image/...` URIs are inert in `` and widely used by feeds, so they must survive + * while `data:` is blocked on navigational/framing attributes (see provideMaliciousHtml()). + */ + public static function test_sanitizeHTML_whenDataImageUri_keepsItOnImageElements(): void { + $png = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; + self::assertSame('', FreshRSS_SimplePieCustom::sanitizeHTML('')); + self::assertStringContainsString('poster="' . $png . '"', FreshRSS_SimplePieCustom::sanitizeHTML('')); + } + + public static function test_sanitizeHTML_whenDisallowedScheme_prefixesUnsafe(): void { + $result = FreshRSS_SimplePieCustom::sanitizeHTML('click'); + self::assertStringContainsString('href="unsafe:data:text/html,x"', $result); + } }