From 435c66b14bfba187f8865f2d6d80af73524918a8 Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Thu, 2 Mar 2023 14:39:35 +0100
Subject: [PATCH] [BUGFIX] Adjust Content-Security-Policy reports check for SVG
 files

Issue #93884 provided and adjusted CSP header for SVG inline styles:
| default-src 'self'; script-src 'none';
| style-src 'unsafe-inline'; object-src 'none';

For SVG files, having 'unsafe-inline' for style-src is fine, since
it only applies to the very same file and cannot include other local
or remote resources.

Resolves: #100041
Releases: main, 12.4, 11.5
Change-Id: I198e0a8225ef6c0e729a3ae78981581b2d2b2205
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79033
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
---
 .../ContentSecurityPolicyHeader.php           |  4 ++-
 .../ServerResponse/FileDeclaration.php        |  2 +-
 .../ServerResponse/ServerResponseCheck.php    |  4 +--
 .../ContentSecurityPolicyHeaderTest.php       | 25 +++++++++++++++++--
 4 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeader.php b/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeader.php
index 3919ab4f8dbd..e86de5f5cf5e 100644
--- a/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeader.php
+++ b/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeader.php
@@ -48,8 +48,9 @@ class ContentSecurityPolicyHeader
         return empty($this->directives);
     }
 
-    public function mitigatesCrossSiteScripting(): bool
+    public function mitigatesCrossSiteScripting(string $fileName = null): bool
     {
+        $isSvg = str_ends_with($fileName ?? '', '.svg');
         $defaultSrc = isset($this->directives['default-src'])
             ? $this->directiveMitigatesCrossSiteScripting($this->directives['default-src'])
             : null;
@@ -58,6 +59,7 @@ class ContentSecurityPolicyHeader
             : null;
         $styleSrc = isset($this->directives['style-src'])
             ? $this->directiveMitigatesCrossSiteScripting($this->directives['style-src'])
+                || ($isSvg && $this->directives['style-src']->hasInstructions('unsafe-inline'))
             : null;
         $objectSrc = isset($this->directives['object-src'])
             ? $this->directiveMitigatesCrossSiteScripting($this->directives['object-src'])
diff --git a/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/FileDeclaration.php b/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/FileDeclaration.php
index 205312369685..b085f1c4a337 100644
--- a/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/FileDeclaration.php
+++ b/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/FileDeclaration.php
@@ -122,7 +122,7 @@ class FileDeclaration
     {
         $mismatches = [];
         if ($this->handler instanceof \Closure) {
-            $result = $this->handler->call($this, $response);
+            $result = $this->handler->call($this, $this, $response);
             if ($result !== null) {
                 $mismatches[] = $result;
             }
diff --git a/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ServerResponseCheck.php b/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ServerResponseCheck.php
index d252a420a582..b686e9d2459c 100644
--- a/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ServerResponseCheck.php
+++ b/typo3/sysext/install/Classes/SystemEnvironment/ServerResponse/ServerResponseCheck.php
@@ -137,7 +137,7 @@ class ServerResponseCheck implements CheckInterface
 
     protected function initializeFileDeclarations(string $fileName): array
     {
-        $cspClosure = function (ResponseInterface $response): ?StatusMessage {
+        $cspClosure = function (FileDeclaration $fileDeclaration, ResponseInterface $response): ?StatusMessage {
             $cspHeader = new ContentSecurityPolicyHeader(
                 $response->getHeaderLine('content-security-policy')
             );
@@ -147,7 +147,7 @@ class ServerResponseCheck implements CheckInterface
                     'missing Content-Security-Policy for this location'
                 );
             }
-            if (!$cspHeader->mitigatesCrossSiteScripting()) {
+            if (!$cspHeader->mitigatesCrossSiteScripting($fileDeclaration->getFileName())) {
                 return new StatusMessage(
                     'weak Content-Security-Policy for this location "%s"',
                     $response->getHeaderLine('content-security-policy')
diff --git a/typo3/sysext/install/Tests/Unit/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeaderTest.php b/typo3/sysext/install/Tests/Unit/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeaderTest.php
index df9342be5853..ac437e2c2fc7 100644
--- a/typo3/sysext/install/Tests/Unit/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeaderTest.php
+++ b/typo3/sysext/install/Tests/Unit/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeaderTest.php
@@ -27,48 +27,69 @@ class ContentSecurityPolicyHeaderTest extends TestCase
         return [
             '#1' => [
                 '',
+                null,
                 false,
             ],
             '#2' => [
                 "default-src 'none'",
+                null,
                 true,
             ],
             '#3' => [
                 "script-src 'none'",
+                null,
                 false,
             ],
             '#4' => [
                 "style-src 'none'",
+                null,
                 false,
             ],
             '#5' => [
                 "default-src 'none'; script-src 'none'",
+                null,
                 true,
             ],
             '#6' => [
                 "default-src 'none'; style-src 'none'",
+                null,
                 true,
             ],
             '#7' => [
                 "default-src 'none'; object-src 'none'",
+                null,
                 true,
             ],
             '#8' => [
                 "default-src 'none'; script-src 'self'; style-src 'self'; object-src 'self'",
+                null,
                 false,
             ],
             '#9' => [
                 "script-src 'none'; style-src 'none'; object-src 'none'",
+                null,
                 true,
             ],
             '#10' => [
                 "default-src 'none'; script-src 'unsafe-eval'; style-src 'none'; object-src 'none'",
+                null,
                 false,
             ],
             '#11' => [
                 "default-src 'none'; script-src 'unsafe-inline'; style-src 'none'; object-src 'none'",
+                null,
                 false,
             ],
+            '#12' => [
+                "default-src 'self'; script-src 'none'; style-src 'unsafe-inline'; object-src 'none'",
+                null,
+                false,
+            ],
+            '#13' => [
+                "default-src 'self'; script-src 'none'; style-src 'unsafe-inline'; object-src 'none'",
+                'file.svg',
+                true,
+            ],
         ];
     }
 
@@ -79,9 +100,9 @@ class ContentSecurityPolicyHeaderTest extends TestCase
      * @test
      * @dataProvider mitigatesCrossSiteScriptingDataProvider
      */
-    public function mitigatesCrossSiteScripting(string $header, bool $expectation): void
+    public function mitigatesCrossSiteScripting(string $header, ?string $fileName, $expectation): void
     {
         $subject = new ContentSecurityPolicyHeader($header);
-        self::assertSame($expectation, $subject->mitigatesCrossSiteScripting());
+        self::assertSame($expectation, $subject->mitigatesCrossSiteScripting($fileName));
     }
 }
-- 
GitLab