[Bug]: Unconditional Session::secure() before login form invalidates session on every GET, causing MismatchedSessionId in browsers
AuthenticationPlugin::authenticate() calls Session::secure() unconditionally
before every rendering of the login form:
/* Show login form (this exits) */
if (! $success) {
/* Force generating of new session */
Session::secure();Session::secure() calls session_regenerate_id(true), so every
unauthenticated GET invalidates the previous session. The login form contains
a hidden set_session field bound to the session that rendered it. If any other
request reaches the server between rendering and submitting the form, the
session cookie is replaced and the POST fails with:
PhpMyAdmin\Exceptions\MismatchedSessionId: Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.
Requests that trigger this in real browsers but not in curl:
- speculative prefetch/prerender from the address bar (Chrome/Edge default)
- the localized messages endpoint (
index.php?route=/messages&l=...) loaded by the login page itself (moved into the routing/auth stack by #17677) - any parallel tab or duplicated request
I believe this explains the long tail of hard-to-reproduce reports of this error message, e.g. #19015 (fails on Chrome/Edge, works on Firefox unless DevTools is open, which changes request behavior) and possibly part of #16922.
To Reproduce
Server-side only, no browser needed:
curl -sS -c /tmp/j.jar https://HOST/ -o /tmp/1.html
curl -sS -b /tmp/j.jar -c /tmp/j.jar https://HOST/ -o /tmp/2.html
grep -o 'name="set_session" value="[^"]*"' /tmp/1.html /tmp/2.htmlThe two set_session values differ: each GET of the login form destroys the
previous session. Submitting the form rendered by request 1 after request 2
happened reproduces the error 100% of the time. Session storage itself is fine
(verified: a plain PHP session with the same cookie name persists across
requests on the same setup).
Expected behavior
Loading the login form multiple times should not invalidate the session, so that the form rendered by any of those requests can still be submitted.
Proposed fix
Session fixation protection is already provided by the Session::secure()
call on successful login (AuthenticationCookie::authenticate()).
Regenerating on every form render is redundant and breaks multi-request
scenarios. Only generating when no token exists yet fixes it:
if (! $success) {
/* Generate session token if missing; regeneration happens on successful login */
if (Session::getToken() === '') {
Session::secure();
}After this change the two curl requests above return identical set_session
values and browser login works reliably. Logout/re-login still rotates the
session id (verified).
Fix commit on my fork: https://github.com/YorkHost-fr/phpmyadmin/commit/d49735e
Happy to open a PR if this approach is acceptable.
Server configuration
- Operating system: Debian 13 (trixie)
- Web server: nginx + PHP-FPM
- PHP version: 8.4.24
- phpMyAdmin version: 6.0.0-dev (current master; verified the code path is identical to master)
Client configuration
- Browser: reproduced on Chromium-based and others; also reproduced entirely server-side with curl (see above)
How to Reproduce
No response
Expected behavior
No response
Screenshots
No response
Operating System
No response
Web Server
No response
Database Server
No response
Database version
No response
PHP version
No response
phpMyAdmin version
6.0.0-dev
browser
No response
Additional context
No response
Source: phpmyadmin/phpmyadmin