From ba66465e0018d66543cb6519828591733ccb2861 Mon Sep 17 00:00:00 2001 From: Frank Naegler Date: Tue, 16 Mar 2021 10:02:56 +0100 Subject: [PATCH] [SECURITY] Add cache for error page handling To prevent DoS attacks by using page-based error handling, the content of the error page is now cached, this prevents fetching the content of the error pages again and again. Resolves: #88824 Releases: master, 11.1, 10.4, 9.5 Change-Id: I6dea5200dc710a182b66deedfbeb2110ea829117 Security-Bulletin: TYPO3-CORE-SA-2021-005 Security-References: CVE-2021-21359 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68430 Tested-by: Oliver Hader Reviewed-by: Oliver Hader --- .../PageContentErrorHandler.php | 65 ++++++++++++++++--- ...ant-88824-AddCacheForErrorPageHandling.rst | 24 +++++++ 2 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Important-88824-AddCacheForErrorPageHandling.rst diff --git a/typo3/sysext/core/Classes/Error/PageErrorHandler/PageContentErrorHandler.php b/typo3/sysext/core/Classes/Error/PageErrorHandler/PageContentErrorHandler.php index 1562280f02f6..f40590c9079d 100644 --- a/typo3/sysext/core/Classes/Error/PageErrorHandler/PageContentErrorHandler.php +++ b/typo3/sysext/core/Classes/Error/PageErrorHandler/PageContentErrorHandler.php @@ -19,10 +19,11 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException; use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\RequestFactory; -use TYPO3\CMS\Core\Http\Response; use TYPO3\CMS\Core\LinkHandling\LinkService; use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException; use TYPO3\CMS\Core\Site\Entity\Site; @@ -47,6 +48,11 @@ class PageContentErrorHandler implements PageErrorHandlerInterface */ protected $errorHandlerConfiguration; + /** + * @var int + */ + protected $pageUid = 0; + /** * PageContentErrorHandler constructor. * @param int $statusCode @@ -68,25 +74,48 @@ public function __construct(int $statusCode, array $configuration) * @param array $reasons * @return ResponseInterface * @throws \RuntimeException + * @throws NoSuchCacheException */ public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface { try { $resolvedUrl = $this->resolveUrl($request, $this->errorHandlerConfiguration['errorContentSource']); - $content = null; - if ($resolvedUrl !== (string)$request->getUri()) { + + $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('pages'); + $cacheIdentifier = 'errorPage_' . md5($resolvedUrl); + $cacheContent = $cache->get($cacheIdentifier); + + if (!$cacheContent && $resolvedUrl !== (string)$request->getUri()) { try { - $subResponse = GeneralUtility::makeInstance(RequestFactory::class)->request($resolvedUrl, 'GET'); + $subResponse = GeneralUtility::makeInstance(RequestFactory::class) + ->request($resolvedUrl, 'GET', $this->getSubRequestOptions()); } catch (\Exception $e) { throw new \RuntimeException('Error handler could not fetch error page "' . $resolvedUrl . '", reason: ' . $e->getMessage(), 1544172838); } if ($subResponse->getStatusCode() >= 300) { throw new \RuntimeException('Error handler could not fetch error page "' . $resolvedUrl . '", status code: ' . $subResponse->getStatusCode(), 1544172839); } - // create new response object and re-use only the body and the content-type of the sub-request - return new Response($subResponse->getBody(), $this->statusCode, [ - 'Content-Type' => $subResponse->getHeader('Content-Type') - ]); + + $body = $subResponse->getBody()->getContents(); + $contentType = $subResponse->getHeader('Content-Type'); + + // Cache body and content-type if sub-response returned a HTTP status 200 + if ($subResponse->getStatusCode() === 200) { + $cacheTags = ['errorPage']; + if ($this->pageUid > 0) { + // Cache Tag "pageId_" ensures, cache is purged when content of 404 page changes + $cacheTags[] = 'pageId_' . $this->pageUid; + } + $cacheContent = [ + 'body' => $body, + 'headers' => ['Content-Type' => $contentType], + ]; + $cache->set($cacheIdentifier, $cacheContent, $cacheTags); + } + } + if ($cacheContent && $cacheContent['body'] && $cacheContent['headers']) { + // We use a HtmlResponse here, since no Stream is available for cached response content + return new HtmlResponse($cacheContent['body'], $this->statusCode, $cacheContent['headers']); } $content = 'The error page could not be resolved, as the error page itself is not accessible'; } catch (InvalidRouteArgumentsException | SiteNotFoundException $e) { @@ -95,6 +124,22 @@ public function handlePageError(ServerRequestInterface $request, string $message return new HtmlResponse($content, $this->statusCode); } + /** + * Returns request options for the subrequest and ensures, that a reasoneable timeout is present + * + * @return array|int[] + */ + protected function getSubRequestOptions(): array + { + $options = []; + if ((int)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['timeout'] === 0) { + $options = [ + 'timeout' => 30 + ]; + } + return $options; + } + /** * Resolve the URL (currently only page and external URL are supported) * @@ -115,8 +160,10 @@ protected function resolveUrl(ServerRequestInterface $request, string $typoLinkU return $urlParams['url']; } + $this->pageUid = (int)$urlParams['pageuid']; + // Get the site related to the configured error page - $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((int)$urlParams['pageuid']); + $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($this->pageUid); // Fall back to current request for the site if (!$site instanceof Site) { $site = $request->getAttribute('site', null); diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-88824-AddCacheForErrorPageHandling.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-88824-AddCacheForErrorPageHandling.rst new file mode 100644 index 000000000000..2688a87e25d1 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Important-88824-AddCacheForErrorPageHandling.rst @@ -0,0 +1,24 @@ +.. include:: ../../Includes.txt + +===================================================== +Important: #88824 - Add cache for error page handling +===================================================== + +See :issue:`88824` + +Description +=========== + +In order to prevent possible DoS attacks when the page-based error handler +is used, the content of the 404 error page is now cached in the TYPO3 +page cache. Any dynamic content on the error page (e.g. content created +by TypoScript or uncached plugins) will therefore also be cached. + +If the 404 error page contains dynamic content, TYPO3 administrators must +ensure that no sensitive data (e.g. username of logged in frontend user) +will be shown on the error page. + +If dynamic content is required on the 404 error page, it is recommended +to implement a custom PHP based error handler. + +.. index:: Backend, ext:backend