Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Session.php
blob3382d4b96ec0a034a04c9071c801ca21fb881e1d
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin;
7 use PhpMyAdmin\Error\ErrorHandler;
8 use PhpMyAdmin\Exceptions\SessionHandlerException;
10 use function htmlspecialchars;
11 use function implode;
12 use function ini_get;
13 use function ini_set;
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;
31 class Session
33 /**
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);
43 /**
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 '])) {
48 return;
51 throw new SessionHandlerException('Failed to generate random CSRF token!');
54 /**
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
69 session_unset();
70 self::generateToken();
73 /**
74 * Session failed function
76 * @param mixed[] $errors PhpMyAdmin\Error\ErrorHandler array
78 * @throws SessionHandlerException
80 private static function sessionFailed(array $errors): void
82 $messages = [];
83 foreach ($errors as $error) {
84 /**
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 '
107 . 'in your browser.'
108 . '<br><br>'
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
122 // request meanwhile
123 @session_destroy();
124 } else {
125 // do not use session_write_close, see issue #13392
126 session_abort();
130 /** @psalm-var 'Lax'|'Strict'|'None' $cookieSameSite */
131 $cookieSameSite = $config->get('CookieSameSite') ?? 'Strict';
132 $cookiePath = $config->getRootPath();
134 session_set_cookie_params([
135 'lifetime' => 0,
136 'path' => $cookiePath,
137 'domain' => '',
138 'secure' => $config->isHttps(),
139 'httponly' => true,
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');
156 // use cookies only
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
172 // proxy servers
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 '])) {
209 return;
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);
227 session_start();
228 if (! empty($_SESSION[' PMA_token '])) {
229 return;
232 throw new SessionHandlerException(
233 'Failed to store CSRF token in session! Probably sessions are not working properly.',