3 * Server config checks management
6 declare(strict_types
=1);
8 namespace PhpMyAdmin\Config
;
11 use PhpMyAdmin\Sanitize
;
12 use PhpMyAdmin\Setup\Index
as SetupIndex
;
16 use function function_exists
;
17 use function htmlspecialchars
;
19 use function mb_strlen
;
20 use function sodium_crypto_secretbox_keygen
;
23 use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES
;
26 * Performs various compatibility, security and consistency checks on current config
28 * Outputs results to message list, must be called between SetupIndex::messagesBegin()
29 * and SetupIndex::messagesEnd()
31 class ServerConfigChecks
33 public function __construct(protected ConfigFile
$cfg)
38 * Perform config checks
40 public function performConfigChecks(): void
42 /** @var string $blowfishSecret */
43 $blowfishSecret = $this->cfg
->get('blowfish_secret', '');
45 $this->performConfigChecksServers($blowfishSecret);
47 // $cfg['AllowArbitraryServer']
49 if ($this->cfg
->getValue('AllowArbitraryServer')) {
50 $sAllowArbitraryServerWarn = sprintf(
52 'This %soption%s should be disabled as it allows attackers to '
53 . 'bruteforce login to any MySQL server. If you feel this is necessary, '
54 . 'use %srestrict login to MySQL server%s or %strusted proxies list%s. '
55 . 'However, IP-based protection with trusted proxies list may not be '
56 . 'reliable if your IP belongs to an ISP where thousands of users, '
57 . 'including you, are connected to.',
59 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
61 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
63 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
66 SetupIndex
::messagesSet(
68 'AllowArbitraryServer',
69 Descriptions
::get('AllowArbitraryServer'),
70 Sanitize
::convertBBCode($sAllowArbitraryServerWarn),
74 $this->performConfigChecksLoginCookie();
76 $sDirectoryNotice = __(
77 'This value should be double checked to ensure that this directory is '
78 . 'neither world accessible nor readable or writable by other users on '
83 // should not be world-accessible
84 if ($this->cfg
->getValue('SaveDir') != '') {
85 SetupIndex
::messagesSet(
88 Descriptions
::get('SaveDir'),
89 Sanitize
::convertBBCode($sDirectoryNotice),
94 // should not be world-accessible
95 if ($this->cfg
->getValue('TempDir') != '') {
96 SetupIndex
::messagesSet(
99 Descriptions
::get('TempDir'),
100 Sanitize
::convertBBCode($sDirectoryNotice),
104 $this->performConfigChecksZips();
108 * Check config of servers
110 * @param string $blowfishSecret Blowfish secret
112 protected function performConfigChecksServers(string $blowfishSecret): void
114 $blowfishSecretSet = false;
116 $serverCnt = $this->cfg
->getServerCount();
117 $isCookieAuthUsed = 0;
118 /** @infection-ignore-all */
119 for ($i = 1; $i <= $serverCnt; $i++
) {
120 $cookieAuthServer = $this->cfg
->getValue('Servers/' . $i . '/auth_type') === 'cookie';
121 $isCookieAuthUsed |
= (int) $cookieAuthServer;
122 $serverName = $this->performConfigChecksServersGetServerName(
123 $this->cfg
->getServerName($i),
126 $serverName = htmlspecialchars($serverName);
128 if ($cookieAuthServer && mb_strlen($blowfishSecret, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES
) {
129 $blowfishSecretSet = true;
130 $this->cfg
->set('blowfish_secret', sodium_crypto_secretbox_keygen());
133 // $cfg['Servers'][$i]['ssl']
134 // should be enabled if possible
135 if (! $this->cfg
->getValue('Servers/' . $i . '/ssl')) {
136 $title = Descriptions
::get('Servers/1/ssl') . ' (' . $serverName . ')';
137 SetupIndex
::messagesSet(
139 'Servers/' . $i . '/ssl',
142 'You should use SSL connections if your database server supports it.',
147 $sSecurityInfoMsg = Sanitize
::convertBBCode(sprintf(
149 'If you feel this is necessary, use additional protection settings - '
150 . '%1$shost authentication%2$s settings and %3$strusted proxies list%4$s. '
151 . 'However, IP-based protection may not be reliable if your IP belongs '
152 . 'to an ISP where thousands of users, including you, are connected to.',
154 '[a@' . Url
::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server_config]',
156 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
160 // $cfg['Servers'][$i]['auth_type']
161 // warn about full user credentials if 'auth_type' is 'config'
163 $this->cfg
->getValue('Servers/' . $i . '/auth_type') === 'config'
164 && $this->cfg
->getValue('Servers/' . $i . '/user') != ''
165 && $this->cfg
->getValue('Servers/' . $i . '/password') != ''
167 $title = Descriptions
::get('Servers/1/auth_type')
168 . ' (' . $serverName . ')';
169 SetupIndex
::messagesSet(
171 'Servers/' . $i . '/auth_type',
173 Sanitize
::convertBBCode(sprintf(
175 'You set the [kbd]config[/kbd] authentication type and included '
176 . 'username and password for auto-login, which is not a desirable '
177 . 'option for live hosts. Anyone who knows or guesses your phpMyAdmin '
178 . 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication '
179 . 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].',
181 '[a@' . Url
::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server]',
184 . ' ' . $sSecurityInfoMsg,
188 // $cfg['Servers'][$i]['AllowRoot']
189 // $cfg['Servers'][$i]['AllowNoPassword']
190 // serious security flaw
192 ! $this->cfg
->getValue('Servers/' . $i . '/AllowRoot')
193 ||
! $this->cfg
->getValue('Servers/' . $i . '/AllowNoPassword')
198 $title = Descriptions
::get('Servers/1/AllowNoPassword')
199 . ' (' . $serverName . ')';
200 SetupIndex
::messagesSet(
202 'Servers/' . $i . '/AllowNoPassword',
204 __('You allow for connecting to the server without a password.')
205 . ' ' . $sSecurityInfoMsg,
209 // $cfg['blowfish_secret']
210 // it's required for 'cookie' authentication
211 if ($isCookieAuthUsed === 0 ||
! $blowfishSecretSet) {
215 // 'cookie' auth used, blowfish_secret was generated
216 SetupIndex
::messagesSet(
218 'blowfish_secret_created',
219 Descriptions
::get('blowfish_secret'),
220 Sanitize
::convertBBCode(__(
221 'You didn\'t have blowfish secret set and have enabled '
222 . '[kbd]cookie[/kbd] authentication, so a key was automatically '
223 . 'generated for you. It is used to encrypt cookies; you don\'t need to '
232 * @param string $serverName Server name
233 * @param int $serverId Server id
235 * @return string Server name
237 protected function performConfigChecksServersGetServerName(
241 if ($serverName === 'localhost') {
242 return $serverName . ' [' . $serverId . ']';
249 * Perform config checks for zip part.
251 protected function performConfigChecksZips(): void
253 $this->performConfigChecksServerGZipdump();
254 $this->performConfigChecksServerBZipdump();
255 $this->performConfigChecksServersZipdump();
259 * Perform config checks for zip part.
261 protected function performConfigChecksServersZipdump(): void
264 // requires zip_open in import
265 if ($this->cfg
->getValue('ZipDump') && ! $this->functionExists('zip_open')) {
266 SetupIndex
::messagesSet(
269 Descriptions
::get('ZipDump'),
270 Sanitize
::convertBBCode(sprintf(
272 '%sZip decompression%s requires functions (%s) which are unavailable on this system.',
274 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
282 // requires gzcompress in export
283 if (! $this->cfg
->getValue('ZipDump') ||
$this->functionExists('gzcompress')) {
287 SetupIndex
::messagesSet(
290 Descriptions
::get('ZipDump'),
291 Sanitize
::convertBBCode(sprintf(
293 '%sZip compression%s requires functions (%s) which are unavailable on this system.',
295 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
303 * Check configuration for login cookie
305 protected function performConfigChecksLoginCookie(): void
307 // $cfg['LoginCookieValidity']
308 // value greater than session.gc_maxlifetime will cause
309 // random session invalidation after that time
310 $loginCookieValidity = $this->cfg
->getValue('LoginCookieValidity');
311 if ($loginCookieValidity > ini_get('session.gc_maxlifetime')) {
312 SetupIndex
::messagesSet(
314 'LoginCookieValidity',
315 Descriptions
::get('LoginCookieValidity'),
316 Sanitize
::convertBBCode(sprintf(
318 '%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may '
319 . 'cause random session invalidation (currently session.gc_maxlifetime '
322 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
324 '[a@' . Core
::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']',
326 ini_get('session.gc_maxlifetime'),
331 // $cfg['LoginCookieValidity']
332 // should be at most 1800 (30 min)
333 if ($loginCookieValidity > 1800) {
334 SetupIndex
::messagesSet(
336 'LoginCookieValidity',
337 Descriptions
::get('LoginCookieValidity'),
338 Sanitize
::convertBBCode(sprintf(
340 '%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) '
341 . 'at most. Values larger than 1800 may pose a security risk such as '
344 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
350 // $cfg['LoginCookieValidity']
351 // $cfg['LoginCookieStore']
352 // LoginCookieValidity must be less or equal to LoginCookieStore
354 $this->cfg
->getValue('LoginCookieStore') == 0
355 ||
$loginCookieValidity <= $this->cfg
->getValue('LoginCookieStore')
360 SetupIndex
::messagesSet(
362 'LoginCookieValidity',
363 Descriptions
::get('LoginCookieValidity'),
364 Sanitize
::convertBBCode(sprintf(
366 'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s '
367 . 'is not 0, %sLogin cookie validity%s must be set to a value less or '
370 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
372 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
379 * Check GZipDump configuration
381 protected function performConfigChecksServerBZipdump(): void
384 // requires bzip2 functions
386 ! $this->cfg
->getValue('BZipDump')
387 ||
($this->functionExists('bzopen') && $this->functionExists('bzcompress'))
392 $functions = $this->functionExists('bzopen') ?
'' : 'bzopen';
393 $functions .= $this->functionExists('bzcompress') ?
'' : ($functions !== '' ?
', ' : '') . 'bzcompress';
394 SetupIndex
::messagesSet(
397 Descriptions
::get('BZipDump'),
398 Sanitize
::convertBBCode(
401 '%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which '
402 . 'are unavailable on this system.',
404 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
413 * Check GZipDump configuration
415 protected function performConfigChecksServerGZipdump(): void
418 // requires zlib functions
420 ! $this->cfg
->getValue('GZipDump')
421 ||
($this->functionExists('gzopen') && $this->functionExists('gzencode'))
426 SetupIndex
::messagesSet(
429 Descriptions
::get('GZipDump'),
430 Sanitize
::convertBBCode(sprintf(
432 '%1$sGZip compression and decompression%2$s requires functions (%3$s) which '
433 . 'are unavailable on this system.',
435 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
443 * Wrapper around function_exists to allow mock in test
445 * @param string $name Function name
447 protected function functionExists(string $name): bool
449 return function_exists($name);