2beb0f053e583a1b13223060d6ba4a6af608184e
[Packages/TYPO3.CMS.git] / typo3 / sysext / reports / Classes / Report / Status / SecurityStatus.php
1 <?php
2 namespace TYPO3\CMS\Reports\Report\Status;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
20 use TYPO3\CMS\Core\Localization\LanguageService;
21 use TYPO3\CMS\Core\Messaging\FlashMessage;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Reports\Status as ReportStatus;
24 use TYPO3\CMS\Reports\StatusProviderInterface;
25 use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory;
26 use TYPO3\CMS\Saltedpasswords\Utility\ExtensionManagerConfigurationUtility;
27 use TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility;
28
29 /**
30 * Performs several checks about the system's health
31 */
32 class SecurityStatus implements StatusProviderInterface
33 {
34 /**
35 * Determines the security of this TYPO3 installation
36 *
37 * @return \TYPO3\CMS\Reports\Status[] List of statuses
38 */
39 public function getStatus()
40 {
41 $statuses = [
42 'trustedHostsPattern' => $this->getTrustedHostsPatternStatus(),
43 'adminUserAccount' => $this->getAdminAccountStatus(),
44 'fileDenyPattern' => $this->getFileDenyPatternStatus(),
45 'htaccessUpload' => $this->getHtaccessUploadStatus(),
46 'saltedpasswords' => $this->getSaltedPasswordsStatus(),
47 ];
48 return $statuses;
49 }
50
51 /**
52 * Checks if the trusted hosts pattern check is disabled.
53 *
54 * @return \TYPO3\CMS\Reports\Status An object representing whether the check is disabled
55 */
56 protected function getTrustedHostsPatternStatus()
57 {
58 $value = $this->getLanguageService()->getLL('status_ok');
59 $message = '';
60 $severity = ReportStatus::OK;
61 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
62 $value = $this->getLanguageService()->getLL('status_insecure');
63 $severity = ReportStatus::ERROR;
64 $message = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.install_trustedhosts');
65 }
66 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_trustedHostsPattern'), $value, $message, $severity);
67 }
68
69 /**
70 * Checks whether a BE user account named admin with default password exists.
71 *
72 * @return \TYPO3\CMS\Reports\Status An object representing whether a default admin account exists
73 */
74 protected function getAdminAccountStatus()
75 {
76 $value = $this->getLanguageService()->getLL('status_ok');
77 $message = '';
78 $severity = ReportStatus::OK;
79
80 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
81 $queryBuilder->getRestrictions()
82 ->removeAll()
83 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
84
85 $row = $queryBuilder
86 ->select('uid', 'username', 'password')
87 ->from('be_users')
88 ->where(
89 $queryBuilder->expr()->eq(
90 'username',
91 $queryBuilder->createNamedParameter('admin', \PDO::PARAM_STR)
92 )
93 )
94 ->execute()
95 ->fetch();
96
97 if (!empty($row)) {
98 $secure = true;
99 /** @var \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface $saltingObject */
100 $saltingObject = SaltFactory::getSaltingInstance($row['password']);
101 if (is_object($saltingObject)) {
102 if ($saltingObject->checkPassword('password', $row['password'])) {
103 $secure = false;
104 }
105 }
106 // Check against plain MD5
107 if ($row['password'] === '5f4dcc3b5aa765d61d8327deb882cf99') {
108 $secure = false;
109 }
110 if (!$secure) {
111 $value = $this->getLanguageService()->getLL('status_insecure');
112 $severity = ReportStatus::ERROR;
113 $editUserAccountUrl = BackendUtility::getModuleUrl(
114 'record_edit',
115 [
116 'edit[be_users][' . $row['uid'] . ']' => 'edit',
117 'returnUrl' => BackendUtility::getModuleUrl('system_ReportsTxreportsm1')
118 ]
119 );
120 $message = sprintf(
121 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.backend_admin'),
122 '<a href="' . htmlspecialchars($editUserAccountUrl) . '">',
123 '</a>'
124 );
125 }
126 }
127 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_adminUserAccount'), $value, $message, $severity);
128 }
129
130 /**
131 * Checks if fileDenyPattern was changed which is dangerous on Apache
132 *
133 * @return \TYPO3\CMS\Reports\Status An object representing whether the file deny pattern has changed
134 */
135 protected function getFileDenyPatternStatus()
136 {
137 $value = $this->getLanguageService()->getLL('status_ok');
138 $message = '';
139 $severity = ReportStatus::OK;
140 $defaultParts = GeneralUtility::trimExplode('|', FILE_DENY_PATTERN_DEFAULT, true);
141 $givenParts = GeneralUtility::trimExplode('|', $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'], true);
142 $result = array_intersect($defaultParts, $givenParts);
143 if ($defaultParts !== $result) {
144 $value = $this->getLanguageService()->getLL('status_insecure');
145 $severity = ReportStatus::ERROR;
146 $message = sprintf(
147 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_deny_pattern_partsNotPresent'),
148 '<br /><pre>' . htmlspecialchars(FILE_DENY_PATTERN_DEFAULT) . '</pre><br />'
149 );
150 }
151 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_fileDenyPattern'), $value, $message, $severity);
152 }
153
154 /**
155 * Checks if fileDenyPattern allows to upload .htaccess files which is
156 * dangerous on Apache.
157 *
158 * @return \TYPO3\CMS\Reports\Status An object representing whether it's possible to upload .htaccess files
159 */
160 protected function getHtaccessUploadStatus()
161 {
162 $value = $this->getLanguageService()->getLL('status_ok');
163 $message = '';
164 $severity = ReportStatus::OK;
165 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] != FILE_DENY_PATTERN_DEFAULT
166 && GeneralUtility::verifyFilenameAgainstDenyPattern('.htaccess')) {
167 $value = $this->getLanguageService()->getLL('status_insecure');
168 $severity = ReportStatus::ERROR;
169 $message = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_deny_htaccess');
170 }
171 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_htaccessUploadProtection'), $value, $message, $severity);
172 }
173
174 /**
175 * Checks whether salted Passwords are configured or not.
176 *
177 * @return \TYPO3\CMS\Reports\Status An object representing the security of the saltedpassswords extension
178 */
179 protected function getSaltedPasswordsStatus()
180 {
181 $value = $this->getLanguageService()->getLL('status_ok');
182 $severity = ReportStatus::OK;
183 /** @var ExtensionManagerConfigurationUtility $configCheck */
184 $configCheck = GeneralUtility::makeInstance(ExtensionManagerConfigurationUtility::class);
185 $message = '<p>' . $this->getLanguageService()->getLL('status_saltedPasswords_infoText') . '</p>';
186 $messageDetail = '';
187 $resultCheck = $configCheck->checkConfigurationBackend([]);
188 switch ($resultCheck['errorType']) {
189 case FlashMessage::INFO:
190 $messageDetail .= $resultCheck['html'];
191 break;
192 case FlashMessage::WARNING:
193 $severity = ReportStatus::WARNING;
194 $messageDetail .= $resultCheck['html'];
195 break;
196 case FlashMessage::ERROR:
197 $value = $this->getLanguageService()->getLL('status_insecure');
198 $severity = ReportStatus::ERROR;
199 $messageDetail .= $resultCheck['html'];
200 break;
201 default:
202 }
203 $unsecureUserCount = SaltedPasswordsUtility::getNumberOfBackendUsersWithInsecurePassword();
204 if ($unsecureUserCount > 0) {
205 $value = $this->getLanguageService()->getLL('status_insecure');
206 $severity = ReportStatus::ERROR;
207 $messageDetail .= '<div class="panel panel-warning">' .
208 '<div class="panel-body">' .
209 $this->getLanguageService()->getLL('status_saltedPasswords_notAllPasswordsHashed') .
210 '</div>' .
211 '</div>';
212 }
213 $message .= $messageDetail;
214 if (empty($messageDetail)) {
215 $message = '';
216 }
217 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_saltedPasswords'), $value, $message, $severity);
218 }
219
220 /**
221 * @return LanguageService
222 */
223 protected function getLanguageService()
224 {
225 return $GLOBALS['LANG'];
226 }
227 }