Session.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Session handling
  5. *
  6. * @package PhpMyAdmin
  7. *
  8. * @see https://secure.php.net/session
  9. */
  10. namespace PhpMyAdmin;
  11. use PhpMyAdmin\Config;
  12. use PhpMyAdmin\Core;
  13. use PhpMyAdmin\ErrorHandler;
  14. use PhpMyAdmin\Util;
  15. /**
  16. * Session class
  17. *
  18. * @package PhpMyAdmin
  19. */
  20. class Session
  21. {
  22. /**
  23. * Generates PMA_token session variable.
  24. *
  25. * @return void
  26. */
  27. private static function generateToken()
  28. {
  29. $_SESSION[' PMA_token '] = Util::generateRandom(16);
  30. /**
  31. * Check if token is properly generated (the generation can fail, for example
  32. * due to missing /dev/random for openssl).
  33. */
  34. if (empty($_SESSION[' PMA_token '])) {
  35. Core::fatalError(
  36. 'Failed to generate random CSRF token!'
  37. );
  38. }
  39. }
  40. /**
  41. * tries to secure session from hijacking and fixation
  42. * should be called before login and after successful login
  43. * (only required if sensitive information stored in session)
  44. *
  45. * @return void
  46. */
  47. public static function secure()
  48. {
  49. // prevent session fixation and XSS
  50. if (session_status() === PHP_SESSION_ACTIVE && ! defined('TESTSUITE')) {
  51. session_regenerate_id(true);
  52. }
  53. // continue with empty session
  54. session_unset();
  55. self::generateToken();
  56. }
  57. /**
  58. * Session failed function
  59. *
  60. * @param array $errors PhpMyAdmin\ErrorHandler array
  61. *
  62. * @return void
  63. */
  64. private static function sessionFailed(array $errors)
  65. {
  66. $messages = array();
  67. foreach ($errors as $error) {
  68. /*
  69. * Remove path from open() in error message to avoid path disclossure
  70. *
  71. * This can happen with PHP 5 when nonexisting session ID is provided,
  72. * since PHP 7, session existence is checked first.
  73. *
  74. * This error can also happen in case of session backed error (eg.
  75. * read only filesystem) on any PHP version.
  76. *
  77. * The message string is currently hardcoded in PHP, so hopefully it
  78. * will not change in future.
  79. */
  80. $messages[] = preg_replace(
  81. '/open\(.*, O_RDWR\)/',
  82. 'open(SESSION_FILE, O_RDWR)',
  83. htmlspecialchars($error->getMessage())
  84. );
  85. }
  86. /*
  87. * Session initialization is done before selecting language, so we
  88. * can not use translations here.
  89. */
  90. Core::fatalError(
  91. 'Error during session start; please check your PHP and/or '
  92. . 'webserver log file and configure your PHP '
  93. . 'installation properly. Also ensure that cookies are enabled '
  94. . 'in your browser.'
  95. . '<br /><br />'
  96. . implode('<br /><br />', $messages)
  97. );
  98. }
  99. /**
  100. * Set up session
  101. *
  102. * @param PhpMyAdmin\Config $config Configuration handler
  103. * @param PhpMyAdmin\ErrorHandler $errorHandler Error handler
  104. * @return void
  105. */
  106. public static function setUp(Config $config, ErrorHandler $errorHandler)
  107. {
  108. // verify if PHP supports session, die if it does not
  109. if (!function_exists('session_name')) {
  110. Core::warnMissingExtension('session', true);
  111. } elseif (! empty(ini_get('session.auto_start'))
  112. && session_name() != 'phpMyAdmin'
  113. && !empty(session_id())) {
  114. // Do not delete the existing non empty session, it might be used by
  115. // other applications; instead just close it.
  116. if (empty($_SESSION)) {
  117. // Ignore errors as this might have been destroyed in other
  118. // request meanwhile
  119. @session_destroy();
  120. } elseif (function_exists('session_abort')) {
  121. // PHP 5.6 and newer
  122. session_abort();
  123. } else {
  124. session_write_close();
  125. }
  126. }
  127. // session cookie settings
  128. session_set_cookie_params(
  129. 0, $config->getRootPath(),
  130. '', $config->isHttps(), true
  131. );
  132. // cookies are safer (use ini_set() in case this function is disabled)
  133. ini_set('session.use_cookies', 'true');
  134. // optionally set session_save_path
  135. $path = $config->get('SessionSavePath');
  136. if (!empty($path)) {
  137. session_save_path($path);
  138. // We can not do this unconditionally as this would break
  139. // any more complex setup (eg. cluster), see
  140. // https://github.com/phpmyadmin/phpmyadmin/issues/8346
  141. ini_set('session.save_handler', 'files');
  142. }
  143. // use cookies only
  144. ini_set('session.use_only_cookies', '1');
  145. // strict session mode (do not accept random string as session ID)
  146. ini_set('session.use_strict_mode', '1');
  147. // make the session cookie HttpOnly
  148. ini_set('session.cookie_httponly', '1');
  149. // do not force transparent session ids
  150. ini_set('session.use_trans_sid', '0');
  151. // delete session/cookies when browser is closed
  152. ini_set('session.cookie_lifetime', '0');
  153. // warn but don't work with bug
  154. ini_set('session.bug_compat_42', 'false');
  155. ini_set('session.bug_compat_warn', 'true');
  156. // use more secure session ids
  157. ini_set('session.hash_function', '1');
  158. // some pages (e.g. stylesheet) may be cached on clients, but not in shared
  159. // proxy servers
  160. session_cache_limiter('private');
  161. $session_name = 'phpMyAdmin';
  162. @session_name($session_name);
  163. // Restore correct sesion ID (it might have been reset by auto started session
  164. if (isset($_COOKIE['phpMyAdmin'])) {
  165. session_id($_COOKIE['phpMyAdmin']);
  166. }
  167. // on first start of session we check for errors
  168. // f.e. session dir cannot be accessed - session file not created
  169. $orig_error_count = $errorHandler->countErrors(false);
  170. $session_result = session_start();
  171. if ($session_result !== true
  172. || $orig_error_count != $errorHandler->countErrors(false)
  173. ) {
  174. setcookie($session_name, '', 1);
  175. $errors = $errorHandler->sliceErrors($orig_error_count);
  176. self::sessionFailed($errors);
  177. }
  178. unset($orig_error_count, $session_result);
  179. /**
  180. * Disable setting of session cookies for further session_start() calls.
  181. */
  182. if(session_status() !== PHP_SESSION_ACTIVE) {
  183. ini_set('session.use_cookies', 'true');
  184. }
  185. /**
  186. * Token which is used for authenticating access queries.
  187. * (we use "space PMA_token space" to prevent overwriting)
  188. */
  189. if (empty($_SESSION[' PMA_token '])) {
  190. self::generateToken();
  191. /**
  192. * Check for disk space on session storage by trying to write it.
  193. *
  194. * This seems to be most reliable approach to test if sessions are working,
  195. * otherwise the check would fail with custom session backends.
  196. */
  197. $orig_error_count = $errorHandler->countErrors();
  198. session_write_close();
  199. if ($errorHandler->countErrors() > $orig_error_count) {
  200. $errors = $errorHandler->sliceErrors($orig_error_count);
  201. self::sessionFailed($errors);
  202. }
  203. session_start();
  204. if (empty($_SESSION[' PMA_token '])) {
  205. Core::fatalError(
  206. 'Failed to store CSRF token in session! ' .
  207. 'Probably sessions are not working properly.'
  208. );
  209. }
  210. }
  211. }
  212. }