Files

248 lines
12 KiB
PHP

<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Reports\Report\Status;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
use TYPO3\CMS\Reports\Status as ReportStatus;
/**
* Performs several checks about the system's health
*/
class SecurityStatus implements RequestAwareStatusProviderInterface
{
/**
* Determines the security of this TYPO3 installation
*
* @param ServerRequestInterface|null $request
* @return ReportStatus[] List of statuses
*/
public function getStatus(?ServerRequestInterface $request = null): array
{
$statuses = [
'trustedHostsPattern' => $this->getTrustedHostsPatternStatus(),
'fileDenyPattern' => $this->getFileDenyPatternStatus(),
'htaccessUpload' => $this->getHtaccessUploadStatus(),
'exceptionHandler' => $this->getExceptionHandlerStatus(),
'exportedFiles' => $this->getExportedFilesStatus(),
];
if ($request !== null) {
$statuses['encryptedConnectionStatus'] = $this->getEncryptedConnectionStatus($request);
$lockSslStatus = $this->getLockSslStatus($request);
if ($lockSslStatus) {
$statuses['getLockSslStatus'] = $lockSslStatus;
}
}
return $statuses;
}
public function getLabel(): string
{
return 'security';
}
/**
* Checks if the current connection is encrypted (HTTPS)
*/
protected function getEncryptedConnectionStatus(ServerRequestInterface $request): ReportStatus
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
$normalizedParams = $request->getAttribute('normalizedParams');
if (!$normalizedParams->isHttps()) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$severity = ContextualFeedbackSeverity::WARNING;
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_encryptedConnectionStatus_insecure');
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_encryptedConnectionStatus'), $value, $message, $severity);
}
/**
* @return ReportStatus
*/
protected function getLockSslStatus(ServerRequestInterface $request): ?ReportStatus
{
$normalizedParams = $request->getAttribute('normalizedParams');
if ($normalizedParams->isHttps()) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL']) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_lockSslStatus_insecure');
$severity = ContextualFeedbackSeverity::WARNING;
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_lockSslStatus'), $value, $message, $severity);
}
return null;
}
/**
* Checks if the trusted hosts pattern check is disabled.
*
* @return ReportStatus An object representing whether the check is disabled
*/
protected function getTrustedHostsPatternStatus(): ReportStatus
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$severity = ContextualFeedbackSeverity::ERROR;
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_trustedhosts');
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_trustedHostsPattern'), $value, $message, $severity);
}
/**
* Checks if fileDenyPattern was changed which is dangerous on Apache
*
* @return ReportStatus An object representing whether the file deny pattern has changed
*/
protected function getFileDenyPatternStatus(): ReportStatus
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
$fileAccessCheck = GeneralUtility::makeInstance(FileNameValidator::class);
if ($fileAccessCheck->missingImportantPatterns()) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$severity = ContextualFeedbackSeverity::ERROR;
$message = sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_pattern_partsNotPresent'),
'<br /><pre>' . htmlspecialchars($fileAccessCheck::DEFAULT_FILE_DENY_PATTERN) . '</pre><br />'
);
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_fileDenyPattern'), $value, $message, $severity);
}
/**
* Checks if fileDenyPattern allows to upload .htaccess files which is
* dangerous on Apache.
*
* @return ReportStatus An object representing whether it's possible to upload .htaccess files
*/
protected function getHtaccessUploadStatus(): ReportStatus
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
$fileNameAccess = GeneralUtility::makeInstance(FileNameValidator::class);
if ($fileNameAccess->customFileDenyPatternConfigured()
&& $fileNameAccess->isValid('.htaccess')) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$severity = ContextualFeedbackSeverity::ERROR;
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_htaccess');
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_htaccessUploadProtection'), $value, $message, $severity);
}
protected function getExceptionHandlerStatus(): ReportStatus
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
if (
str_contains($GLOBALS['TYPO3_CONF_VARS']['SYS']['productionExceptionHandler'], 'Debug')
|| (Environment::getContext()->isProduction() && (int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] === 1)
) {
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$severity = ContextualFeedbackSeverity::ERROR;
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exceptionHandler_errorMessage');
} elseif ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] === 1) {
$severity = ContextualFeedbackSeverity::WARNING;
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exceptionHandler_warningMessage');
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exceptionHandler'), $value, $message, $severity);
}
protected function getExportedFilesStatus(): ReportStatus
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file');
$exportedFiles = $queryBuilder
->select('storage', 'identifier')
->from('sys_file')
->where(
$queryBuilder->expr()->like(
'identifier',
$queryBuilder->createNamedParameter('%/_temp_/importexport/%')
),
$queryBuilder->expr()->or(
$queryBuilder->expr()->like(
'identifier',
$queryBuilder->createNamedParameter('%.xml')
),
$queryBuilder->expr()->like(
'identifier',
$queryBuilder->createNamedParameter('%.t3d')
)
),
)
->executeQuery()
->fetchAllAssociative();
if (count($exportedFiles) > 0) {
$files = [];
foreach ($exportedFiles as $exportedFile) {
$files[] = '<li>' . htmlspecialchars($exportedFile['storage'] . ':' . $exportedFile['identifier']) . '</li>';
}
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
$severity = ContextualFeedbackSeverity::WARNING;
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exportedFiles_warningMessage');
$message .= '<ul>' . implode(PHP_EOL, $files) . '</ul>';
$message .= $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exportedFiles_warningRecommendation');
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exportedFiles'), $value, $message, $severity);
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}