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