Skip to content
Snippets Groups Projects
Commit d339f493 authored by Frank Naegler's avatar Frank Naegler Committed by Oliver Hader
Browse files

[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/+/68438


Tested-by: default avatarOliver Hader <oliver.hader@typo3.org>
Reviewed-by: default avatarOliver Hader <oliver.hader@typo3.org>
parent 1bd00950
Branches
Tags
No related merge requests found
......@@ -19,10 +19,11 @@ namespace TYPO3\CMS\Core\Error\PageErrorHandler;
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 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
* @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 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
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 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
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);
......
.. 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
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment