I have a problem with some session features and the way Chrome does prefetch / render. I am trying to link a piece of forum software (esoTalk) with a laravel 4.3 user application. I have authentication event listeners that force laravel to create a php session (also a built-in laravel session) that allows the forum and application to share authentication information. When accessing the forum, and if the user is not logged in, but this general information exists (i.e., the user has registered with the laravel application), the forum will register this user using the information available in the session.
For the most part, this works great, except that prefetching Chromes seems to break things. If I monitor the forum using the debugger, I see that when I type the URL of the forum, but before I click enter chrome, you will get access to the forum. Following the debugger, I see that he does everything he needs and successfully logs into the system. As a last step, the forum will regenerate the session identifier to stop capturing. Here it breaks. It looks like chrome is ignoring the new session id (sent via the http SetCookie header), so when I press Enter, I go to the forum (and make a completely new request) using the original session id. This identifier does not exist, so I get the setting with a new one and, therefore, lose my registered status. To the user it just looks like they never logged in.
I tried my best to figure out how I could get around this. I do not want to remove the session id regeneration, as it serves a security purpose. I also cannot disable prefetching / rendering. All in all, I seem to be a little pickle.
I created some code that replicates this. Although it relies on a preliminary attack (so you will need to hit each of the files through the address bar several times)
// test1.php <?php function regenerateToken() { session_regenerate_id(true); $_SESSION["token"] = substr(md5(uniqid(rand())), 0, 13); $_SESSION["userAgent"] = md5($_SERVER["HTTP_USER_AGENT"]); } // Start a session. session_set_cookie_params(0, '/'); session_name("SessionBork_Test_session"); session_start(); $_SESSION["SentryUserId"] = '99'; regenerateToken(); header('Content-Type: text/plain'); foreach ($_SESSION as $k => $v) { echo $k . " = " . $v . "\n"; }
Access test1.php, followed by test2.php, and you should see a bunch of session variable output. As soon as prerendering / fetching begins to appear, you will begin to receive a broken message.
// test2.php <?php function regenerateToken() { session_regenerate_id(true); $_SESSION["token"] = substr(md5(uniqid(rand())), 0, 13); $_SESSION["userAgent"] = md5($_SERVER["HTTP_USER_AGENT"]); } // Start a session. session_set_cookie_params(0, '/'); session_name("SessionBork_Test_session"); session_start(); if (empty($_SESSION["token"])) regenerateToken(); // Complicate session highjacking - check the current user agent against the one that initiated the session. if (md5($_SERVER["HTTP_USER_AGENT"]) != $_SESSION["userAgent"]) session_destroy(); // Log in a the user based on the SentryUserId // ... logging in, setting userId, regenerating session $_SESSION["userId"] = '10'; regenerateToken(); header('Content-Type: text/plain'); foreach ($_SESSION as $k => $v) { echo $k . " = " . $v . "\n"; } if ( ! isset($_SESSION['SentryUserId'])) echo "\n--\nPrerendering brokeded me.";
If you can connect it to xdebug in the IDE or something, you will see that the hidden prerender got into test2.php (which looks absolutely correct in the answer), and then the subsequent actual press, when you press enter, where it forgot, who you are.