CVE-2022-36107: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: TYPO3 is an open source PHP based web content management system released under the GNU GPL. It has been discovered that the `FileDumpController` (backend and frontend context) is vulnerable to cross-site scripting when malicious files are displayed using this component. A valid backend user account is needed to exploit this vulnerability. Update to TYPO3 version 7.6.58 ELTS, 8.7.48 ELTS, 9.5.37 ELTS, 10.4.32 or 11.5.16 that fix the problem. There are no known workarounds for this issue.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2022-09-13T18:15Z
Repository: https://github.com/TYPO3/typo3
Language: PHP
Files changed: typo3/sysext/core/Classes/Controller/FileDumpController.php
Commit date: 2022-09-13 10:09:53 +0200
Commit message: [SECURITY] Mitigate cross-site-scripting in FileDumpController

FileDumpController is used to expose stored files from the backend
user interface through a corresponding service-side process. Since
content-security-policy settings for files served directly by the
web server won't be applied, FileDumpController has to take care.

Resolves: #98221
Releases: main, 11.5, 10.4
Change-Id: I4fde10e48e33fa08452eddf876172f56b4f38e28
Security-Bulletin: TYPO3-CORE-SA-2022-009
Security-References: CVE-2022-36107
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/75718
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>

Diff: diff --git a/typo3/sysext/core/Classes/Controller/FileDumpController.php b/typo3/sysext/core/Classes/Controller/FileDumpController.php
index 422a15e2e004..0a1d919c6fd3 100644
--- a/typo3/sysext/core/Classes/Controller/FileDumpController.php
+++ b/typo3/sysext/core/Classes/Controller/FileDumpController.php
@@ -27,12 +27,14 @@ use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\FileReference;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ProcessedFileRepository;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceInterface;
 use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
  * Class FileDumpController
  */
 class FileDumpController
@@ -75,11 +77,11 @@ class FileDumpController
         // Allow some other process to do some security/access checks.
         // Event Listeners should return a 403 response if access is rejected
         $event = new ModifyFileDumpEvent($file, $request);
         $event = $this->eventDispatcher->dispatch($event);
         if ($event->isPropagationStopped()) {
-            return $event->getResponse();
+            return $this->applyContentSecurityPolicy($event->getFile(), $event->getResponse());
         }
         $file = $event->getFile();
 
         $processingInstructions = [];
 
@@ -118,14 +120,17 @@ class FileDumpController
                 $file = $file->getOriginalFile();
             }
             $file = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingInstructions);
         }
 
-        return $file->getStorage()->streamFile(
+        return $this->applyContentSecurityPolicy(
             $file,
-            (bool)($parameters['dl'] ?? false),
-            $parameters['fn'] ?? null
+            $file->getStorage()->streamFile(
+                $file,
+                (bool)($parameters['dl'] ?? false),
+                $parameters['fn'] ?? null
+            )
         );
     }
 
     protected function buildParametersFromRequest(ServerRequestInterface $request): array
     {
@@ -228,6 +233,19 @@ class FileDumpController
     {
         return $file->getStorage()->getDriverType() !== 'Local'
             || GeneralUtility::makeInstance(FileNameValidator::class)
                 ->isValid(basename($file->getIdentifier()));
     }
+
+    /**
+     * Applies hard-coded content-security-policy (CSP) for file to be dumped.
+     */
+    protected function applyContentSecurityPolicy(ResourceInterface $file, ResponseInterface $response): ResponseInterface
+    {
+        $extension = PathUtility::pathinfo($file->getName(), PATHINFO_EXTENSION);
+        // same as in `typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/resources-root-htaccess`
+        $policy = $extension === 'pdf' || $response->getHeaderLine('content-type') === 'application/pdf'
+            ? "default-src 'self' 'unsafe-inline'; script-src 'none'; object-src 'self'; plugin-types application/pdf;"
+            : "default-src 'self'; script-src 'none'; style-src 'none'; object-src 'none';";
+        return $response->withAddedHeader('content-security-policy', $policy);
+    }
 }

Vulnerable code:             return $event->getResponse();
        return $file->getStorage()->streamFile(
            (bool)($parameters['dl'] ?? false),
            $parameters['fn'] ?? null
Fixed code: use TYPO3\CMS\Core\Resource\ResourceInterface;
use TYPO3\CMS\Core\Utility\PathUtility;
            return $this->applyContentSecurityPolicy($event->getFile(), $event->getResponse());
        return $this->applyContentSecurityPolicy(
            $file->getStorage()->streamFile(
                $file,
                (bool)($parameters['dl'] ?? false),
                $parameters['fn'] ?? null
            )

    /**
     * Applies hard-coded content-security-policy (CSP) for file to be dumped.
     */
    protected function applyContentSecurityPolicy(ResourceInterface $file, ResponseInterface $response): ResponseInterface
    {
        $extension = PathUtility::pathinfo($file->getName(), PATHINFO_EXTENSION);
        // same as in `typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/resources-root-htaccess`
        $policy = $extension === 'pdf' || $response->getHeaderLine('content-type') === 'application/pdf'
            ? "default-src 'self' 'unsafe-inline'; script-src 'none'; object-src 'self'; plugin-types application/pdf;"
            : "default-src 'self'; script-src 'none'; style-src 'none'; object-src 'none';";
        return $response->withAddedHeader('content-security-policy', $policy);
    }