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