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