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