Wie man eine benutzerdefinierte TYPO3 Middleware für bessere CSP Sicherheit erstellt?

Moderne Web-Sicherheit erfordert strengere Content-Security-Policy (CSP) Regeln.
Doch Inline Skripte brechen traditionelle CSP Implementierungen. Hier kommen CSP Nonces ins Spiel.

Diese Anleitung zeigt Ihnen, wie Sie eine benutzerdefinierte TYPO3 Middleware erstellen, die CSP Header mit Nonces für Inline-Skripte generiert. Wir werden eine vollständige funktionierende Lösung mit der richtigen Konfiguration und Tests erstellen.

Warum CSP Nonces wichtig

Traditionelle CSP blockiert standardmäßig alle Inline-Skripte. Nonces ermöglichen es Ihnen, bestimmte Inline-Skripte zuzulassen, indem sie mit einem einzigartigen, unvorhersehbaren Token versehen werden. Jede Seitenladung erhält einen neuen Nonce.

Der Browser führt nur Inline Skripte aus, die mit dem aktuellen Nonce übereinstimmen. Dies stoppt XSS Angriffe, während Ihr Inline Code funktionsfähig bleibt.

Projektstruktur

Hier ist werden wir bauen:

Classes/
├── Middleware/
│   └── CspNonceMiddleware.php
├── Service/
│   └── CspNonceService.php
└── ViewHelpers/
    └── CspNonceViewHelper.php
Configuration/
├── RequestMiddlewares.php
└── Services.yaml
Tests/
└── Functional/
    └── Middleware/
        └── CspNonceMiddlewareTest.php

Der CSP Nonce Service

Zuerst erstellen lass uns einen Service, der Nonces generiert und verwaltet:

<?php
declare(strict_types=1);

namespace Vendor\Extension\Service;

use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Site\Entity\Site;

class CspNonceService implements SingletonInterface
{
    private string $currentNonce = '';
    
    public function generateNonce(): string
    {
        $this->currentNonce = base64_encode(random_bytes(16));
        return $this->currentNonce;
    }
    
    public function getCurrentNonce(): string
    {
        if (empty($this->currentNonce)) {
            $this->generateNonce();
        }
        return $this->currentNonce;
    }
    
    public function buildCspHeader(Site $site = null): string
    {
        $nonce = $this->getCurrentNonce();
        
        // Basic CSP with script nonce
        $csp = "default-src 'self'; script-src 'self' 'nonce-{$nonce}'; style-src 'self' 'unsafe-inline';";
        
        // Add site-specific policies if needed
        if ($site) {
            $config = $site->getConfiguration();
            if (isset($config['csp_additional_sources'])) {
                $additional = $config['csp_additional_sources'];
                $csp .= " connect-src 'self' {$additional};";
            }
        }
        
        return $csp;
    }
}

Die Middleware Implementierung

Nun die Haupt-Middleware, die CSP-Header zu den Antworten hinzufügt:

<?php
declare(strict_types=1);

namespace Vendor\Extension\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Site\Entity\Site;
use Vendor\Extension\Service\CspNonceService;

class CspNonceMiddleware implements MiddlewareInterface
{
    private CspNonceService $cspService;
    
    public function __construct(CspNonceService $cspService)
    {
        $this->cspService = $cspService;
    }
    
    public function process(
        ServerRequestInterface $request, 
        RequestHandlerInterface $handler
    ): ResponseInterface {
        
        // Generate nonce before processing request
        $this->cspService->generateNonce();
        
        // Continue with request processing
        $response = $handler->handle($request);
        
        // Only add CSP header for HTML responses
        $contentType = $response->getHeaderLine('Content-Type');
        if (!str_contains($contentType, 'text/html')) {
            return $response;
        }
        
        // Get current site for site-specific policies
        $site = $request->getAttribute('site');
        
        // Build and add CSP header
        $cspHeader = $this->cspService->buildCspHeader($site);
        
        return $response->withHeader('Content-Security-Policy', $cspHeader);
    }
}

ViewHelper für Templates

Erstellen Sie einen ViewHelper, um Nonces in Ihren Fluid-Templates auszugeben:

<?php
declare(strict_types=1);

namespace Vendor\Extension\ViewHelpers;

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use Vendor\Extension\Service\CspNonceService;

class CspNonceViewHelper extends AbstractViewHelper
{
    public static function renderStatic(
        array $arguments,
        \Closure $renderChildrenClosure,
        RenderingContextInterface $renderingContext
    ): string {
        $cspService = GeneralUtility::makeInstance(CspNonceService::class);
        return $cspService->getCurrentNonce();
    }
}

Konfigurationsdateien

Registrieren Sie die Middleware in Configuration/RequestMiddlewares.php:

<?php

return [
    'frontend' => [
        'vendor/extension/csp-nonce' => [
            'target' => \Vendor\Extension\Middleware\CspNonceMiddleware::class,
            'after' => [
                'typo3/cms-frontend/site',
            ],
            'before' => [
                'typo3/cms-frontend/output-compression',
            ],
        ],
    ],
];

Konfigurieren Sie die Abhängigkeitsinjektion in Configuration/Services.yaml:

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Vendor\Extension\:
    resource: '../Classes/*'

  Vendor\Extension\Service\CspNonceService:
    public: true
    
  Vendor\Extension\Middleware\CspNonceMiddleware:
    arguments:
      $cspService: '@Vendor\Extension\Service\CspNonceService'

Verwendung von Nonces in Templates

Fügen Sie in Ihren Fluid-Templates den Nonce zu Inline-Skripten hinzu:

In your Fluid templates, add the nonce to inline scripts:
{namespace csp=Vendor\Extension\ViewHelpers}

<script nonce="{csp:cspNonce()}">
    // Your inline JavaScript here
    console.log('This script will execute');
</script>

<!-- This script will be blocked -->
<script>
    console.log('This script will be blocked by CSP');
</script>

Site-spezifische CSP Richtlinien

Konfigurieren Sie site-spezifische CSP Quellen in Ihrer Seitenkonfiguration:

# config/sites/main/config.yaml
rootPageId: 1
base: 'https://example.com/'
csp_additional_sources: 'https://cdn.example.com https://analytics.google.com'

Die Middleware übernimmt diese Einstellungen automatisch und fügt sie dem CSP Header hinzu.

Testen mit den Browser-Entwicklertools

  • Öffnen Sie Ihre TYPO3 site in Chrome oder Firefox
  • Drücken Sie F12, um die Entwicklertools zu öffnen
  • Gehen Sie zum Reiter "Netzwerk" und laden Sie die Seite neu
  • Suchen Sie nach Ihrer Seitenanforderung und überprüfen Sie die Antwort-Header
  • Suchen Sie nach dem Header Content-Security-Policy

Sie sollten etwas Ähnliches sehen wie:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-ABC123xyz789'; style-src 'self' 'unsafe-inline';

Überprüfen Sie die Registerkarte „Konsole“ auf CSP Verstöße. Blockierte Skripte werden mit roten Fehlermeldungen angezeigt.

Funktionale Prüfung

Erstellen Sie einen Test, um zu überprüfen, ob Ihre Middleware funktioniert:

<?php
declare(strict_types=1);

namespace Vendor\Extension\Tests\Functional\Middleware;

use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Http\Response;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\Extension\Middleware\CspNonceMiddleware;
use Vendor\Extension\Service\CspNonceService;

class CspNonceMiddlewareTest extends FunctionalTestCase
{
    protected array $testExtensionsToLoad = [
        'typo3conf/ext/your_extension',
    ];

    /**
     * @test
     */
    public function middlewareAddsCspHeaderToHtmlResponse(): void
    {
        $cspService = new CspNonceService();
        $middleware = new CspNonceMiddleware($cspService);
        
        $request = new ServerRequest('https://example.com/');
        
        $handler = new class() implements \Psr\Http\Server\RequestHandlerInterface {
            public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
            {
                return (new Response())
                    ->withHeader('Content-Type', 'text/html; charset=utf-8');
            }
        };
        
        $response = $middleware->process($request, $handler);
        
        self::assertTrue($response->hasHeader('Content-Security-Policy'));
        
        $cspHeader = $response->getHeaderLine('Content-Security-Policy');
        self::assertStringContainsString('script-src', $cspHeader);
        self::assertStringContainsString('nonce-', $cspHeader);
    }
    
    /**
     * @test
     */
    public function middlewareSkipsNonHtmlResponses(): void
    {
        $cspService = new CspNonceService();
        $middleware = new CspNonceMiddleware($cspService);
        
        $request = new ServerRequest('https://example.com/api/data');
        
        $handler = new class() implements \Psr\Http\Server\RequestHandlerInterface {
            public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
            {
                return (new Response())
                    ->withHeader('Content-Type', 'application/json');
            }
        };
        
        $response = $middleware->process($request, $handler);
        
        self::assertFalse($response->hasHeader('Content-Security-Policy'));
    }
}

Laufen Sie die Tests mit:

vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml Tests/Functional/

Was ist nächste

Diese Implementierung bietet Ihnen eine solide Grundlage für CSP mit Nonces. Sie können sie erweitern, indem Sie:

  • Style-Nonces hinzufügen (ähnlich wie Script-Nonces)
  • Eine Admin-Oberfläche für das CSP-Policy-Management erstellen
  • CSP-Verstoß-Meldeschwellen hinzufügen
  • Hash-basierte CSP-Unterstützung für statische Skripte hinzufügen

Der Middleware-Ansatz hält die CSP-Logik von Ihrem Geschäftscode getrennt. Da er früh im Anfragezyklus ausgeführt wird, erfasst er alle HTML-Antworten automatisch.

Ihre TYPO3-Website blockiert nun unbefugte Inline-Skripte, während Ihr legitimer Code weiterhin funktioniert. Das ist moderne Web-Sicherheit, die richtig umgesetzt wird.

Post a Comment

×

Got answer to the question you were looking for?