Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with session_regenerate_id and Chrome prefetch/render

I have an issue with some session functionality and the way Chrome does prefetching/rendering. I am attempting to interface a piece of forum software (esoTalk) with a custom laravel 4.3 app. I have authentication event listeners that cause laravel to create a php session (in addition the built in laravel session) that allows the forum and app to share authentication details. On access of the forum, and if the user is not logged in - but this shared information exists (i.e. the user has logged in on the laravel app), the forum will log in that user using the information available in the session.

For the most part this works fine except Chromes prefetching appears to be breaking things. If I monitor the forum using a debugger I can see that when I type out the forum url but before I hit enter chrome will access the forum. Following through with the debugger I can see it does everything it needs to do and is successful in logging in. As a final step the forum regenerates the session id to stop hijacking. This is where it breaks. It looks like chrome ignores the new session id (sent via a http SetCookie header) so that when I hit enter I go to the forum (and make a whole new request) using the original session id. This id doesn't exist so I get set up with a new one and consequently lose my logged in status. To the user this just looks like they never got logged in.

I've googled high and low for suggestions as to how I can work around this. I'm loath to removed the session id regeneration as it does serve a security purpose. I also can't disable the chrome prefetching/rendering. All in all I appear to be in a bit of a pickle.

I have created some code that replicates this. Though it relies on the prerendering kicking in (so you'll need to hit each of the files via the address bar a number of 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 kicks in you'll start getting 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 get it hooked up to xdebug in an IDE or something you should see the hidden prerender hit to test2.php (which looks to be absolutely correct in the response) and then the subsequent actual hit when you press enter where it's forgotten who you are.

like image 778
Adam Cooper Avatar asked Nov 10 '22 01:11

Adam Cooper


1 Answers

One way around this issue would be to detect pre-fetches, and not generate a new session ID on those loads. See this Stack Overflow for information on detecting prefetching in various browsers: HTTP header to detect a preload request by Google Chrome

Additionally, I would argue that there are better ways to go about prevention of session hijacking (e.g. tying the session to an IP address, browser signature, etc.)

Additionally, there may be a second bug in your code: calling session_destroy() destroys the session AND closes out the user's session. You need to call session_start() prior to calling session_regenerate_id(). See the documentation here and the examples here.

like image 167
John M. Avatar answered Nov 14 '22 23:11

John M.