From 6da592d2a5766ca232437f03339e9f2059a53d86 Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Sat, 1 Apr 2023 11:37:02 +0200
Subject: [PATCH] [TASK] Add initiator to <f:sanitize.html> view helper
 invocation

With this update, whenever the `<f:sanitize.html>` view helper is
invoked, the initiator reference will be included in the log. This
will help to quickly identify which class or component triggered
the sanitization process and take necessary actions if needed.

Resolves: #100377
Releases: main, 11.5
Change-Id: Ic7b06a7e96d98492c69e917a575b9e6f2a3d7296
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/78382
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
---
 .../Html/DefaultSanitizerBuilderTest.php      | 43 +++++++++++++++++
 .../ViewHelpers/Sanitize/HtmlViewHelper.php   |  8 +++-
 .../Sanitize/Fixtures/Template.html           |  9 ++++
 .../Sanitize/HtmlViewHelperTest.php           | 46 +++++++++++++++++++
 4 files changed, 105 insertions(+), 1 deletion(-)
 create mode 100644 typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/Fixtures/Template.html

diff --git a/typo3/sysext/core/Tests/Functional/Html/DefaultSanitizerBuilderTest.php b/typo3/sysext/core/Tests/Functional/Html/DefaultSanitizerBuilderTest.php
index a7f52eb242f8..6e4d74335d11 100644
--- a/typo3/sysext/core/Tests/Functional/Html/DefaultSanitizerBuilderTest.php
+++ b/typo3/sysext/core/Tests/Functional/Html/DefaultSanitizerBuilderTest.php
@@ -17,8 +17,12 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Tests\Functional\Html;
 
+use Psr\Log\LogLevel;
 use TYPO3\CMS\Core\Html\DefaultSanitizerBuilder;
 use TYPO3\CMS\Core\Html\SanitizerBuilderFactory;
+use TYPO3\CMS\Core\Html\SanitizerInitiator;
+use TYPO3\CMS\Core\Log\LogRecord;
+use TYPO3\CMS\Core\Tests\Functional\Fixtures\Log\DummyWriter;
 use TYPO3\CMS\Core\Tests\Functional\Html\Fixtures\ExtendedSanitizerBuilder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\HtmlSanitizer\Behavior;
@@ -32,6 +36,26 @@ class DefaultSanitizerBuilderTest extends FunctionalTestCase
      */
     protected $initializeDatabase = false;
 
+    protected $configurationToUseInTestInstance = [
+        'LOG' => [
+            'TYPO3' => [
+                'HtmlSanitizer' => [
+                    'writerConfiguration' => [
+                        LogLevel::DEBUG => [
+                            DummyWriter::class => [],
+                        ],
+                    ],
+                ],
+            ],
+        ],
+    ];
+
+    protected function tearDown(): void
+    {
+        parent::tearDown();
+        DummyWriter::$logs = [];
+    }
+
     public static function isSanitizedDataProvider(): array
     {
         return [
@@ -187,6 +211,25 @@ class DefaultSanitizerBuilderTest extends FunctionalTestCase
         );
     }
 
+    /**
+     * @test
+     */
+    public function incidentIsLogged(): void
+    {
+        $trace = bin2hex(random_bytes(8));
+        $sanitizer = (new DefaultSanitizerBuilder())->build();
+        $sanitizer->sanitize('<script>alert(1)</script>', new SanitizerInitiator($trace));
+        $logItemDataExpectation = [
+            'behavior' => 'default',
+            'nodeName' => 'script',
+            'initiator' => $trace,
+        ];
+        $logItem = end(DummyWriter::$logs);
+        self::assertInstanceOf(LogRecord::class, $logItem);
+        self::assertSame($logItemDataExpectation, $logItem->getData());
+        self::assertSame('TYPO3.HtmlSanitizer.Visitor.CommonVisitor', $logItem->getComponent());
+    }
+
     private function resolveBehaviorFromSanitizer(Sanitizer $sanitizer): Behavior
     {
         $visitorsProp = (new \ReflectionObject($sanitizer))->getProperty('visitors');
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Sanitize/HtmlViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Sanitize/HtmlViewHelper.php
index 03c91b398103..17a0ae3513e9 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/Sanitize/HtmlViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/Sanitize/HtmlViewHelper.php
@@ -18,6 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Fluid\ViewHelpers\Sanitize;
 
 use TYPO3\CMS\Core\Html\SanitizerBuilderFactory;
+use TYPO3\CMS\Core\Html\SanitizerInitiator;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\HtmlSanitizer\Builder\BuilderInterface;
 use TYPO3\HtmlSanitizer\Sanitizer;
@@ -94,7 +95,12 @@ class HtmlViewHelper extends AbstractViewHelper
     {
         $value = $renderChildrenClosure();
         $build = $arguments['build'] ?? 'default';
-        return static::createSanitizer($build)->sanitize((string)$value);
+        return static::createSanitizer($build)->sanitize((string)$value, self::createInitiator());
+    }
+
+    protected static function createInitiator(): SanitizerInitiator
+    {
+        return GeneralUtility::makeInstance(SanitizerInitiator::class, self::class);
     }
 
     protected static function createSanitizer(string $build): Sanitizer
diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/Fixtures/Template.html b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/Fixtures/Template.html
new file mode 100644
index 000000000000..1b7f37c0579a
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/Fixtures/Template.html
@@ -0,0 +1,9 @@
+<html
+    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
+    xmlns:sg="http://typo3.org/ns/TYPO3/CMS/Styleguide/ViewHelpers"
+    data-namespace-typo3-fluid="true"
+>
+
+<f:sanitize.html>{payload}</f:sanitize.html>
+
+</html>
diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/HtmlViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/HtmlViewHelperTest.php
index a688f1d9c429..e9aa43f1027d 100644
--- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/HtmlViewHelperTest.php
+++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Sanitize/HtmlViewHelperTest.php
@@ -17,8 +17,12 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers\Sanitize;
 
+use Psr\Log\LogLevel;
+use TYPO3\CMS\Core\Log\LogRecord;
+use TYPO3\CMS\Core\Tests\Functional\Fixtures\Log\DummyWriter;
 use TYPO3\CMS\Core\Tests\Functional\Html\DefaultSanitizerBuilderTest;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Fluid\ViewHelpers\Sanitize\HtmlViewHelper;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
 class HtmlViewHelperTest extends FunctionalTestCase
@@ -28,6 +32,26 @@ class HtmlViewHelperTest extends FunctionalTestCase
      */
     protected $initializeDatabase = false;
 
+    protected $configurationToUseInTestInstance = [
+        'LOG' => [
+            'TYPO3' => [
+                'HtmlSanitizer' => [
+                    'writerConfiguration' => [
+                        LogLevel::DEBUG => [
+                            DummyWriter::class => [],
+                        ],
+                    ],
+                ],
+            ],
+        ],
+    ];
+
+    protected function tearDown(): void
+    {
+        parent::tearDown();
+        DummyWriter::$logs = [];
+    }
+
     public static function isSanitizedDataProvider(): array
     {
         // @todo splitter for functional tests cannot deal with external classes
@@ -60,4 +84,26 @@ class HtmlViewHelperTest extends FunctionalTestCase
         $view->setTemplateSource('{payload -> f:sanitize.html()}');
         self::assertSame($expectation, $view->render());
     }
+
+    /**
+     * @test
+     */
+    public function incidentIsLogged(): void
+    {
+        $templatePath = __DIR__ . '/Fixtures/Template.html';
+        $view = new StandaloneView();
+        $view->setTemplatePathAndFilename($templatePath);
+        $view->assign('payload', '<script>alert(1)</script>');
+        $view->render();
+
+        $logItemDataExpectation = [
+            'behavior' => 'default',
+            'nodeName' => 'script',
+            'initiator' => HtmlViewHelper::class,
+        ];
+        $logItem = end(DummyWriter::$logs);
+        self::assertInstanceOf(LogRecord::class, $logItem);
+        self::assertSame($logItemDataExpectation, $logItem->getData());
+        self::assertSame('TYPO3.HtmlSanitizer.Visitor.CommonVisitor', $logItem->getComponent());
+    }
 }
-- 
GitLab