Add `AddAcpSecurityHeaders` middleware
authorTim Düsterhus <duesterhus@woltlab.com>
Thu, 19 May 2022 14:35:45 +0000 (16:35 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Fri, 20 May 2022 07:23:44 +0000 (09:23 +0200)
wcfsetup/install/files/lib/http/middleware/AddAcpSecurityHeaders.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/request/RequestHandler.class.php

diff --git a/wcfsetup/install/files/lib/http/middleware/AddAcpSecurityHeaders.class.php b/wcfsetup/install/files/lib/http/middleware/AddAcpSecurityHeaders.class.php
new file mode 100644 (file)
index 0000000..429ca52
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+namespace wcf\http\middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\http\LegacyPlaceholderResponse;
+use wcf\system\request\RequestHandler;
+
+/**
+ * Add various security headers to harden ACP responses.
+ *
+ * @author  Tim Duesterhus
+ * @copyright   2001-2022 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Http\Middleware
+ * @since   5.6
+ */
+final class AddAcpSecurityHeaders implements MiddlewareInterface
+{
+    /**
+     * @inheritDoc
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if (!RequestHandler::getInstance()->isACPRequest()) {
+            return $handler->handle($request);
+        }
+
+        // Also set the headers using the regular `\header()` call, because we might receive a
+        // LegacyPlaceholderResponse and we also need to protect requests to legacy controllers.
+        // If a proper PSR-7 response is returned the headers will be removed again and set on
+        // the response object.
+        \header('referrer-policy: same-origin');
+        \header('cross-origin-opener-policy: same-origin');
+        \header('cross-origin-resource-policy: same-site');
+
+        $response = $handler->handle($request);
+
+        if ($response instanceof LegacyPlaceholderResponse) {
+            return $response;
+        }
+
+        \header_remove('referrer-policy');
+        \header_remove('cross-origin-opener-policy');
+        \header_remove('cross-origin-resource-policy');
+
+        return $response->withHeader('referrer-policy', 'same-origin')
+            ->withHeader('cross-origin-opener-policy', 'same-origin')
+            ->withHeader('cross-origin-resource-policy', 'same-site');
+    }
+}
index 43a93b59dbe348d9115058bc9c50f9b4aa0ec64a..819dd3c6bfd634fa6ecaf6148929d15931e44834 100644 (file)
@@ -5,6 +5,7 @@ namespace wcf\system\request;
 use Laminas\Diactoros\ServerRequestFactory;
 use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
 use wcf\http\LegacyPlaceholderResponse;
+use wcf\http\middleware\AddAcpSecurityHeaders;
 use wcf\http\middleware\EnforceCacheControlPrivate;
 use wcf\http\middleware\EnforceFrameOptions;
 use wcf\http\Pipeline;
@@ -98,13 +99,8 @@ class RequestHandler extends SingletonFactory
 
             $this->checkOfflineMode();
 
-            if ($this->isACPRequest()) {
-                \header('referrer-policy: same-origin');
-                \header('cross-origin-opener-policy: same-origin');
-                \header('cross-origin-resource-policy: same-site');
-            }
-
             $pipeline = new Pipeline([
+                new AddAcpSecurityHeaders(),
                 new EnforceCacheControlPrivate(),
                 new EnforceFrameOptions(),
             ]);