c170f4e87f7e37451f6bf764462ae8eb4305f6da
[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\Messaging\FlashMessage;
21 use TYPO3\CMS\Core\TypoScript\ConfigurationForm;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Lang\LanguageService;
24 use TYPO3\CMS\Reports\Status as ReportStatus;
25 use TYPO3\CMS\Reports\StatusProviderInterface;
26 use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory;
27 use TYPO3\CMS\Saltedpasswords\Utility\ExtensionManagerConfigurationUtility;
28 use TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility;
29
30 /**
31 * Performs several checks about the system's health
32 */
33 class SecurityStatus implements StatusProviderInterface
34 {
35 /**
36 * Determines the security of this TYPO3 installation
37 *
38 * @return \TYPO3\CMS\Reports\Status[] List of statuses
39 */
40 public function getStatus()
41 {
42 $statuses = [
43 'trustedHostsPattern' => $this->getTrustedHostsPatternStatus(),
44 'adminUserAccount' => $this->getAdminAccountStatus(),
45 'encryptionKeyEmpty' => $this->getEncryptionKeyStatus(),
46 'fileDenyPattern' => $this->getFileDenyPatternStatus(),
47 'htaccessUpload' => $this->getHtaccessUploadStatus(),
48 'saltedpasswords' => $this->getSaltedPasswordsStatus(),
49 'cacheFloodingProtection' => $this->getCacheFloodingProtectionStatus()
50 ];
51 return $statuses;
52 }
53
54 /**
55 * @return \TYPO3\CMS\Reports\Status An object representing whether the check is disabled
56 */
57 protected function getCacheFloodingProtectionStatus()
58 {
59 $value = $this->getLanguageService()->getLL('status_ok');
60 $message = '';
61 $severity = ReportStatus::OK;
62 if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['cHashIncludePageId'])) {
63 $value = $this->getLanguageService()->getLL('status_insecure');
64 $severity = ReportStatus::ERROR;
65 $message = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.install_cache_flooding');
66 }
67 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_cacheFloodingProtection'), $value, $message, $severity);
68 }
69
70 /**
71 * Checks if the trusted hosts pattern check is disabled.
72 *
73 * @return \TYPO3\CMS\Reports\Status An object representing whether the check is disabled
74 */
75 protected function getTrustedHostsPatternStatus()
76 {
77 $value = $this->getLanguageService()->getLL('status_ok');
78 $message = '';
79 $severity = ReportStatus::OK;
80 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
81 $value = $this->getLanguageService()->getLL('status_insecure');
82 $severity = ReportStatus::ERROR;
83 $message = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.install_trustedhosts');
84 }
85 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_trustedHostsPattern'), $value, $message, $severity);
86 }
87
88 /**
89 * Checks whether a BE user account named admin with default password exists.
90 *
91 * @return \TYPO3\CMS\Reports\Status An object representing whether a default admin account exists
92 */
93 protected function getAdminAccountStatus()
94 {
95 $value = $this->getLanguageService()->getLL('status_ok');
96 $message = '';
97 $severity = ReportStatus::OK;
98
99 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
100 $queryBuilder->getRestrictions()
101 ->removeAll()
102 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
103
104 $row = $queryBuilder
105 ->select('uid', 'username', 'password')
106 ->from('be_users')
107 ->where(
108 $queryBuilder->expr()->eq(
109 'username',
110 $queryBuilder->createNamedParameter('admin', \PDO::PARAM_STR)
111 )
112 )
113 ->execute()
114 ->fetch();
115
116 if (!empty($row)) {
117 $secure = true;
118 /** @var \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface $saltingObject */
119 $saltingObject = SaltFactory::getSaltingInstance($row['password']);
120 if (is_object($saltingObject)) {
121 if ($saltingObject->checkPassword('password', $row['password'])) {
122 $secure = false;
123 }
124 }
125 // Check against plain MD5
126 if ($row['password'] === '5f4dcc3b5aa765d61d8327deb882cf99') {
127 $secure = false;
128 }
129 if (!$secure) {
130 $value = $this->getLanguageService()->getLL('status_insecure');
131 $severity = ReportStatus::ERROR;
132 $editUserAccountUrl = BackendUtility::getModuleUrl(
133 'record_edit',
134 [
135 'edit[be_users][' . $row['uid'] . ']' => 'edit',
136 'returnUrl' => BackendUtility::getModuleUrl('system_ReportsTxreportsm1')
137 ]
138 );
139 $message = sprintf(
140 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.backend_admin'),
141 '<a href="' . htmlspecialchars($editUserAccountUrl) . '">',
142 '</a>'
143 );
144 }
145 }
146 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_adminUserAccount'), $value, $message, $severity);
147 }
148
149 /**
150 * Checks whether the encryption key is empty.
151 *
152 * @return \TYPO3\CMS\Reports\Status An object representing whether the encryption key is empty or not
153 */
154 protected function getEncryptionKeyStatus()
155 {
156 $value = $this->getLanguageService()->getLL('status_ok');
157 $message = '';
158 $severity = ReportStatus::OK;
159 if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
160 $value = $this->getLanguageService()->getLL('status_insecure');
161 $severity = ReportStatus::ERROR;
162 $url = 'install/index.php?redirect_url=index.php' . urlencode('?TYPO3_INSTALL[type]=config#set_encryptionKey');
163 $message = sprintf(
164 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.install_encryption'),
165 '<a href="' . $url . '">',
166 '</a>'
167 );
168 }
169 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_encryptionKey'), $value, $message, $severity);
170 }
171
172 /**
173 * Checks if fileDenyPattern was changed which is dangerous on Apache
174 *
175 * @return \TYPO3\CMS\Reports\Status An object representing whether the file deny pattern has changed
176 */
177 protected function getFileDenyPatternStatus()
178 {
179 $value = $this->getLanguageService()->getLL('status_ok');
180 $message = '';
181 $severity = ReportStatus::OK;
182 $defaultParts = GeneralUtility::trimExplode('|', FILE_DENY_PATTERN_DEFAULT, true);
183 $givenParts = GeneralUtility::trimExplode('|', $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'], true);
184 $result = array_intersect($defaultParts, $givenParts);
185 if ($defaultParts !== $result) {
186 $value = $this->getLanguageService()->getLL('status_insecure');
187 $severity = ReportStatus::ERROR;
188 $message = sprintf(
189 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_deny_pattern_partsNotPresent'),
190 '<br /><pre>' . htmlspecialchars(FILE_DENY_PATTERN_DEFAULT) . '</pre><br />'
191 );
192 }
193 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_fileDenyPattern'), $value, $message, $severity);
194 }
195
196 /**
197 * Checks if fileDenyPattern allows to upload .htaccess files which is
198 * dangerous on Apache.
199 *
200 * @return \TYPO3\CMS\Reports\Status An object representing whether it's possible to upload .htaccess files
201 */
202 protected function getHtaccessUploadStatus()
203 {
204 $value = $this->getLanguageService()->getLL('status_ok');
205 $message = '';
206 $severity = ReportStatus::OK;
207 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] != FILE_DENY_PATTERN_DEFAULT
208 && GeneralUtility::verifyFilenameAgainstDenyPattern('.htaccess')) {
209 $value = $this->getLanguageService()->getLL('status_insecure');
210 $severity = ReportStatus::ERROR;
211 $message = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_deny_htaccess');
212 }
213 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_htaccessUploadProtection'), $value, $message, $severity);
214 }
215
216 /**
217 * Checks whether salted Passwords are configured or not.
218 *
219 * @return \TYPO3\CMS\Reports\Status An object representing the security of the saltedpassswords extension
220 */
221 protected function getSaltedPasswordsStatus()
222 {
223 $value = $this->getLanguageService()->getLL('status_ok');
224 $severity = ReportStatus::OK;
225 /** @var ExtensionManagerConfigurationUtility $configCheck */
226 $configCheck = GeneralUtility::makeInstance(ExtensionManagerConfigurationUtility::class);
227 $message = '<p>' . $this->getLanguageService()->getLL('status_saltedPasswords_infoText') . '</p>';
228 $messageDetail = '';
229 $resultCheck = $configCheck->checkConfigurationBackend([], new ConfigurationForm());
230 switch ($resultCheck['errorType']) {
231 case FlashMessage::INFO:
232 $messageDetail .= $resultCheck['html'];
233 break;
234 case FlashMessage::WARNING:
235 $severity = ReportStatus::WARNING;
236 $messageDetail .= $resultCheck['html'];
237 break;
238 case FlashMessage::ERROR:
239 $value = $this->getLanguageService()->getLL('status_insecure');
240 $severity = ReportStatus::ERROR;
241 $messageDetail .= $resultCheck['html'];
242 break;
243 default:
244 }
245 $unsecureUserCount = SaltedPasswordsUtility::getNumberOfBackendUsersWithInsecurePassword();
246 if ($unsecureUserCount > 0) {
247 $value = $this->getLanguageService()->getLL('status_insecure');
248 $severity = ReportStatus::ERROR;
249 $messageDetail .= '<div class="panel panel-warning">' .
250 '<div class="panel-body">' .
251 $this->getLanguageService()->getLL('status_saltedPasswords_notAllPasswordsHashed') .
252 '</div>' .
253 '</div>';
254 }
255 $message .= $messageDetail;
256 if (empty($messageDetail)) {
257 $message = '';
258 }
259 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_saltedPasswords'), $value, $message, $severity);
260 }
261
262 /**
263 * @return LanguageService
264 */
265 protected function getLanguageService()
266 {
267 return $GLOBALS['LANG'];
268 }
269 }