Skip to content

Commit

Permalink
[SECURITY] Add cache for error page handling
Browse files Browse the repository at this point in the history
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 <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
  • Loading branch information
NeoBlack authored and ohader committed Mar 16, 2021
1 parent 11eb857 commit ba66465
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 9 deletions.
Expand Up @@ -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;
Expand All @@ -47,6 +48,11 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
*/
protected $errorHandlerConfiguration;

/**
* @var int
*/
protected $pageUid = 0;

/**
* PageContentErrorHandler constructor.
* @param int $statusCode
Expand All @@ -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) {
Expand All @@ -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)
*
Expand All @@ -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);
Expand Down
@@ -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

0 comments on commit ba66465

Please sign in to comment.