3 declare(strict_types
=1);
7 use PhpMyAdmin\Error\ErrorHandler
;
8 use PhpMyAdmin\Exceptions\SessionHandlerException
;
10 use function htmlspecialchars
;
14 use function preg_replace
;
15 use function session_abort
;
16 use function session_cache_limiter
;
17 use function session_destroy
;
18 use function session_id
;
19 use function session_name
;
20 use function session_regenerate_id
;
21 use function session_save_path
;
22 use function session_set_cookie_params
;
23 use function session_start
;
24 use function session_status
;
25 use function session_unset
;
26 use function session_write_close
;
27 use function setcookie
;
29 use const PHP_SESSION_ACTIVE
;
34 * Generates PMA_token session variable.
36 * @throws SessionHandlerException
38 private static function generateToken(): void
40 $_SESSION[' PMA_token '] = Util
::generateRandom(16, true);
41 $_SESSION[' HMAC_secret '] = Util
::generateRandom(16);
44 * Check if token is properly generated (the generation can fail, for example
45 * due to missing /dev/random for openssl).
47 if (! empty($_SESSION[' PMA_token '])) {
51 throw new SessionHandlerException('Failed to generate random CSRF token!');
55 * tries to secure session from hijacking and fixation
56 * should be called before login and after successful login
57 * (only required if sensitive information stored in session)
59 * @throws SessionHandlerException
61 public static function secure(): void
63 // prevent session fixation and XSS
64 if (session_status() === PHP_SESSION_ACTIVE
) {
65 session_regenerate_id(true);
68 // continue with empty session
70 self
::generateToken();
74 * Session failed function
76 * @param mixed[] $errors PhpMyAdmin\Error\ErrorHandler array
78 * @throws SessionHandlerException
80 private static function sessionFailed(array $errors): void
83 foreach ($errors as $error) {
85 * Remove path from open() in error message to avoid path disclossure
87 * This can happen with PHP 5 when nonexisting session ID is provided,
88 * since PHP 7, session existence is checked first.
90 * This error can also happen in case of session backed error (eg.
91 * read only filesystem) on any PHP version.
93 * The message string is currently hardcoded in PHP, so hopefully it
94 * will not change in future.
96 $messages[] = preg_replace(
97 '/open\(.*, O_RDWR\)/',
98 'open(SESSION_FILE, O_RDWR)',
99 htmlspecialchars($error->getMessage()),
103 // Session initialization is done before selecting language, so we can not use translations here.
104 $errorMessage = 'Error during session start; please check your PHP and/or '
105 . 'webserver log file and configure your PHP '
106 . 'installation properly. Also ensure that cookies are enabled '
109 . implode('<br><br>', $messages);
111 throw new SessionHandlerException($errorMessage);
114 /** @throws SessionHandlerException */
115 public static function setUp(Config
$config, ErrorHandler
$errorHandler): void
117 if (! empty(ini_get('session.auto_start')) && session_name() !== 'phpMyAdmin' && ! empty(session_id())) {
118 // Do not delete the existing non empty session, it might be used by
119 // other applications; instead just close it.
120 if ($_SESSION === []) {
121 // Ignore errors as this might have been destroyed in other
125 // do not use session_write_close, see issue #13392
130 /** @psalm-var 'Lax'|'Strict'|'None' $cookieSameSite */
131 $cookieSameSite = $config->get('CookieSameSite') ??
'Strict';
132 $cookiePath = $config->getRootPath();
134 session_set_cookie_params([
136 'path' => $cookiePath,
138 'secure' => $config->isHttps(),
140 'samesite' => $cookieSameSite,
143 // cookies are safer (use ini_set() in case this function is disabled)
144 ini_set('session.use_cookies', 'true');
146 // optionally set session_save_path
147 $path = $config->get('SessionSavePath');
148 if (! empty($path)) {
149 session_save_path($path);
150 // We can not do this unconditionally as this would break
151 // any more complex setup (eg. cluster), see
152 // https://github.com/phpmyadmin/phpmyadmin/issues/8346
153 ini_set('session.save_handler', 'files');
157 ini_set('session.use_only_cookies', '1');
158 // strict session mode (do not accept random string as session ID)
159 ini_set('session.use_strict_mode', '1');
160 // make the session cookie HttpOnly
161 ini_set('session.cookie_httponly', '1');
162 // add SameSite to the session cookie
163 ini_set('session.cookie_samesite', $cookieSameSite);
165 // do not force transparent session ids
166 ini_set('session.use_trans_sid', '0');
168 // delete session/cookies when browser is closed
169 ini_set('session.cookie_lifetime', '0');
171 // some pages (e.g. stylesheet) may be cached on clients, but not in shared
173 session_cache_limiter('private');
175 $httpCookieName = $config->getCookieName('phpMyAdmin');
176 @session_name
($httpCookieName);
178 // Restore correct session ID (it might have been reset by auto started session
179 if ($config->issetCookie('phpMyAdmin')) {
180 session_id($config->getCookie('phpMyAdmin'));
183 // on first start of session we check for errors
184 // f.e. session dir cannot be accessed - session file not created
185 $origErrorCount = $errorHandler->countErrors(false);
187 $sessionResult = session_start();
189 if (! $sessionResult ||
$origErrorCount !== $errorHandler->countErrors(false)) {
190 setcookie($httpCookieName, '', 1);
191 $errors = $errorHandler->sliceErrors($origErrorCount);
192 self
::sessionFailed($errors);
195 unset($origErrorCount, $sessionResult);
198 * Disable setting of session cookies for further session_start() calls.
200 if (session_status() !== PHP_SESSION_ACTIVE
) {
201 ini_set('session.use_cookies', 'true');
205 * Token which is used for authenticating access queries.
206 * (we use "space PMA_token space" to prevent overwriting)
208 if (! empty($_SESSION[' PMA_token '])) {
212 self
::generateToken();
215 * Check for disk space on session storage by trying to write it.
217 * This seems to be most reliable approach to test if sessions are working,
218 * otherwise the check would fail with custom session backends.
220 $origErrorCount = $errorHandler->countErrors();
221 session_write_close();
222 if ($errorHandler->countErrors() > $origErrorCount) {
223 $errors = $errorHandler->sliceErrors($origErrorCount);
224 self
::sessionFailed($errors);
228 if (! empty($_SESSION[' PMA_token '])) {
232 throw new SessionHandlerException(
233 'Failed to store CSRF token in session! Probably sessions are not working properly.',