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
;
17 use function function_exists
;
18 use function htmlspecialchars
;
21 use function preg_match
;
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 /** @var ConfigFile configurations being checked */
37 * @param ConfigFile $cfg Configuration
39 public function __construct(ConfigFile
$cfg)
45 * Perform config checks
49 public function performConfigChecks()
51 $blowfishSecret = $this->cfg
->get('blowfish_secret');
52 $blowfishSecretSet = false;
53 $cookieAuthUsed = false;
55 [$cookieAuthUsed, $blowfishSecret, $blowfishSecretSet]
56 = $this->performConfigChecksServers(
62 $this->performConfigChecksCookieAuthUsed(
68 // $cfg['AllowArbitraryServer']
70 if ($this->cfg
->getValue('AllowArbitraryServer')) {
71 $sAllowArbitraryServerWarn = sprintf(
73 'This %soption%s should be disabled as it allows attackers to '
74 . 'bruteforce login to any MySQL server. If you feel this is necessary, '
75 . 'use %srestrict login to MySQL server%s or %strusted proxies list%s. '
76 . 'However, IP-based protection with trusted proxies list may not be '
77 . 'reliable if your IP belongs to an ISP where thousands of users, '
78 . 'including you, are connected to.'
80 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
82 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
84 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
87 SetupIndex
::messagesSet(
89 'AllowArbitraryServer',
90 Descriptions
::get('AllowArbitraryServer'),
91 Sanitize
::sanitizeMessage($sAllowArbitraryServerWarn)
95 $this->performConfigChecksLoginCookie();
97 $sDirectoryNotice = __(
98 'This value should be double checked to ensure that this directory is '
99 . 'neither world accessible nor readable or writable by other users on '
104 // should not be world-accessible
105 if ($this->cfg
->getValue('SaveDir') != '') {
106 SetupIndex
::messagesSet(
109 Descriptions
::get('SaveDir'),
110 Sanitize
::sanitizeMessage($sDirectoryNotice)
115 // should not be world-accessible
116 if ($this->cfg
->getValue('TempDir') != '') {
117 SetupIndex
::messagesSet(
120 Descriptions
::get('TempDir'),
121 Sanitize
::sanitizeMessage($sDirectoryNotice)
125 $this->performConfigChecksZips();
129 * Check config of servers
131 * @param bool $cookieAuthUsed Cookie auth is used
132 * @param string $blowfishSecret Blowfish secret
133 * @param bool $blowfishSecretSet Blowfish secret set
137 protected function performConfigChecksServers(
142 $serverCnt = $this->cfg
->getServerCount();
143 for ($i = 1; $i <= $serverCnt; $i++
) {
145 = ($this->cfg
->getValue('Servers/' . $i . '/auth_type') === 'cookie');
146 $cookieAuthUsed |
= $cookieAuthServer;
147 $serverName = $this->performConfigChecksServersGetServerName(
148 $this->cfg
->getServerName($i),
151 $serverName = htmlspecialchars($serverName);
153 [$blowfishSecret, $blowfishSecretSet]
154 = $this->performConfigChecksServersSetBlowfishSecret(
160 // $cfg['Servers'][$i]['ssl']
161 // should be enabled if possible
162 if (! $this->cfg
->getValue('Servers/' . $i . '/ssl')) {
163 $title = Descriptions
::get('Servers/1/ssl') . ' (' . $serverName . ')';
164 SetupIndex
::messagesSet(
166 'Servers/' . $i . '/ssl',
169 'You should use SSL connections if your database server '
175 $sSecurityInfoMsg = Sanitize
::sanitizeMessage(sprintf(
177 'If you feel this is necessary, use additional protection settings - '
178 . '%1$shost authentication%2$s settings and %3$strusted proxies list%4$s. '
179 . 'However, IP-based protection may not be reliable if your IP belongs '
180 . 'to an ISP where thousands of users, including you, are connected to.'
182 '[a@' . Url
::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server_config]',
184 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
188 // $cfg['Servers'][$i]['auth_type']
189 // warn about full user credentials if 'auth_type' is 'config'
191 $this->cfg
->getValue('Servers/' . $i . '/auth_type') === 'config'
192 && $this->cfg
->getValue('Servers/' . $i . '/user') != ''
193 && $this->cfg
->getValue('Servers/' . $i . '/password') != ''
195 $title = Descriptions
::get('Servers/1/auth_type')
196 . ' (' . $serverName . ')';
197 SetupIndex
::messagesSet(
199 'Servers/' . $i . '/auth_type',
201 Sanitize
::sanitizeMessage(sprintf(
203 'You set the [kbd]config[/kbd] authentication type and included '
204 . 'username and password for auto-login, which is not a desirable '
205 . 'option for live hosts. Anyone who knows or guesses your phpMyAdmin '
206 . 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication '
207 . 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].'
209 '[a@' . Url
::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server]',
212 . ' ' . $sSecurityInfoMsg
216 // $cfg['Servers'][$i]['AllowRoot']
217 // $cfg['Servers'][$i]['AllowNoPassword']
218 // serious security flaw
220 ! $this->cfg
->getValue('Servers/' . $i . '/AllowRoot')
221 ||
! $this->cfg
->getValue('Servers/' . $i . '/AllowNoPassword')
226 $title = Descriptions
::get('Servers/1/AllowNoPassword')
227 . ' (' . $serverName . ')';
228 SetupIndex
::messagesSet(
230 'Servers/' . $i . '/AllowNoPassword',
232 __('You allow for connecting to the server without a password.')
233 . ' ' . $sSecurityInfoMsg
245 * Set blowfish secret
247 * @param string|null $blowfishSecret Blowfish secret
248 * @param bool $cookieAuthServer Cookie auth is used
249 * @param bool $blowfishSecretSet Blowfish secret set
253 protected function performConfigChecksServersSetBlowfishSecret(
258 if ($cookieAuthServer && $blowfishSecret === null) {
259 $blowfishSecretSet = true;
260 $this->cfg
->set('blowfish_secret', Util
::generateRandom(32));
272 * @param string $serverName Server name
273 * @param int $serverId Server id
275 * @return string Server name
277 protected function performConfigChecksServersGetServerName(
281 if ($serverName === 'localhost') {
282 return $serverName . ' [' . $serverId . ']';
289 * Perform config checks for zip part.
293 protected function performConfigChecksZips()
295 $this->performConfigChecksServerGZipdump();
296 $this->performConfigChecksServerBZipdump();
297 $this->performConfigChecksServersZipdump();
301 * Perform config checks for zip part.
305 protected function performConfigChecksServersZipdump()
308 // requires zip_open in import
309 if ($this->cfg
->getValue('ZipDump') && ! $this->functionExists('zip_open')) {
310 SetupIndex
::messagesSet(
313 Descriptions
::get('ZipDump'),
314 Sanitize
::sanitizeMessage(sprintf(
316 '%sZip decompression%s requires functions (%s) which are unavailable '
319 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
327 // requires gzcompress in export
328 if (! $this->cfg
->getValue('ZipDump') ||
$this->functionExists('gzcompress')) {
332 SetupIndex
::messagesSet(
335 Descriptions
::get('ZipDump'),
336 Sanitize
::sanitizeMessage(sprintf(
338 '%sZip compression%s requires functions (%s) which are unavailable on '
341 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
349 * Check config of servers
351 * @param bool $cookieAuthUsed Cookie auth is used
352 * @param bool $blowfishSecretSet Blowfish secret set
353 * @param string $blowfishSecret Blowfish secret
357 protected function performConfigChecksCookieAuthUsed(
362 // $cfg['blowfish_secret']
363 // it's required for 'cookie' authentication
364 if (! $cookieAuthUsed) {
368 if ($blowfishSecretSet) {
369 // 'cookie' auth used, blowfish_secret was generated
370 SetupIndex
::messagesSet(
372 'blowfish_secret_created',
373 Descriptions
::get('blowfish_secret'),
374 Sanitize
::sanitizeMessage(__(
375 'You didn\'t have blowfish secret set and have enabled '
376 . '[kbd]cookie[/kbd] authentication, so a key was automatically '
377 . 'generated for you. It is used to encrypt cookies; you don\'t need to '
382 $blowfishWarnings = [];
384 if (strlen($blowfishSecret) < 32) {
386 $blowfishWarnings[] = __(
387 'Key is too short, it should have at least 32 characters.'
391 // check used characters
392 $hasDigits = (bool) preg_match('/\d/', $blowfishSecret);
393 $hasChars = (bool) preg_match('/\S/', $blowfishSecret);
394 $hasNonword = (bool) preg_match('/\W/', $blowfishSecret);
395 if (! $hasDigits ||
! $hasChars ||
! $hasNonword) {
396 $blowfishWarnings[] = Sanitize
::sanitizeMessage(
398 'Key should contain letters, numbers [em]and[/em] '
399 . 'special characters.'
404 if (! empty($blowfishWarnings)) {
405 SetupIndex
::messagesSet(
407 'blowfish_warnings' . count($blowfishWarnings),
408 Descriptions
::get('blowfish_secret'),
409 implode('<br>', $blowfishWarnings)
416 * Check configuration for login cookie
420 protected function performConfigChecksLoginCookie()
422 // $cfg['LoginCookieValidity']
423 // value greater than session.gc_maxlifetime will cause
424 // random session invalidation after that time
425 $loginCookieValidity = $this->cfg
->getValue('LoginCookieValidity');
427 $loginCookieValidity > ini_get('session.gc_maxlifetime')
429 SetupIndex
::messagesSet(
431 'LoginCookieValidity',
432 Descriptions
::get('LoginCookieValidity'),
433 Sanitize
::sanitizeMessage(sprintf(
435 '%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may '
436 . 'cause random session invalidation (currently session.gc_maxlifetime '
439 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
441 '[a@' . Core
::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']',
443 ini_get('session.gc_maxlifetime')
448 // $cfg['LoginCookieValidity']
449 // should be at most 1800 (30 min)
450 if ($loginCookieValidity > 1800) {
451 SetupIndex
::messagesSet(
453 'LoginCookieValidity',
454 Descriptions
::get('LoginCookieValidity'),
455 Sanitize
::sanitizeMessage(sprintf(
457 '%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) '
458 . 'at most. Values larger than 1800 may pose a security risk such as '
461 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
467 // $cfg['LoginCookieValidity']
468 // $cfg['LoginCookieStore']
469 // LoginCookieValidity must be less or equal to LoginCookieStore
471 ($this->cfg
->getValue('LoginCookieStore') == 0)
472 ||
($loginCookieValidity <= $this->cfg
->getValue('LoginCookieStore'))
477 SetupIndex
::messagesSet(
479 'LoginCookieValidity',
480 Descriptions
::get('LoginCookieValidity'),
481 Sanitize
::sanitizeMessage(sprintf(
483 'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s '
484 . 'is not 0, %sLogin cookie validity%s must be set to a value less or '
487 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
489 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
496 * Check GZipDump configuration
500 protected function performConfigChecksServerBZipdump()
503 // requires bzip2 functions
505 ! $this->cfg
->getValue('BZipDump')
506 ||
($this->functionExists('bzopen') && $this->functionExists('bzcompress'))
511 $functions = $this->functionExists('bzopen')
514 $functions .= $this->functionExists('bzcompress')
516 : ($functions ?
', ' : '') . 'bzcompress';
517 SetupIndex
::messagesSet(
520 Descriptions
::get('BZipDump'),
521 Sanitize
::sanitizeMessage(
524 '%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which '
525 . 'are unavailable on this system.'
527 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
536 * Check GZipDump configuration
540 protected function performConfigChecksServerGZipdump()
543 // requires zlib functions
545 ! $this->cfg
->getValue('GZipDump')
546 ||
($this->functionExists('gzopen') && $this->functionExists('gzencode'))
551 SetupIndex
::messagesSet(
554 Descriptions
::get('GZipDump'),
555 Sanitize
::sanitizeMessage(sprintf(
557 '%1$sGZip compression and decompression%2$s requires functions (%3$s) which '
558 . 'are unavailable on this system.'
560 '[a@' . Url
::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
568 * Wrapper around function_exists to allow mock in test
570 * @param string $name Function name
574 protected function functionExists($name)
576 return function_exists($name);