Drupal 10: From cookies to user sessions
When you need to examine user session tokens, you know you’re deep in the bowels of the CMS. That’s what happened to me recently, as I was debugging why CloudFlare was mixing up user sessions and giving admin access to otherwise unpermissioned users
To help debug this, I needed a way to associate user cookies with entries from the sessions
table. I wrote a drush script to do exactly that: Given the value of the SESSXXX cookie in your browser, the script will find the corresponding sessions
entry and dump its information, decoding the session metadata in the process:
$ drush scr export_sessions.php -- --cookie=5XvW3NGG8q1PcCrEXn676THvQBitaUwDiPw8XzAgXtihV43u
[
{
"uid": "1",
"sid": "-Xcm0ar3mWcMhIhhBANA3K-jUx3JNOsu190LPEUzIN8",
"hostname": "172.24.0.1",
"timestamp": "1724179990",
"session": "_sf2_attributes|a:1:{s:3:\"uid\";s:1:\"1\";}_sf2_meta|a:4:{s:1:\"u\";i:1724179990;s:1:\"c\";i:1723574737;s:1:\"l\";i:2000000;s:1:\"s\";s:43:\"OCpNT7IvSsWNfPeYXam7E7XFPTKqb-8qWPUTMe8MFlQ\";}",
"sf2": [
{
"name": "attributes",
"value": {
"uid": "1"
}
},
{
"name": "meta",
"value": {
"u": 1724179990,
"c": 1723574737,
"l": 2000000,
"s": "OCpNT7IvSsWNfPeYXam7E7XFPTKqb-8qWPUTMe8MFlQ"
}
}
]
}
]
The same script will dump ALL sessions if you don’t pass in a cookie value. Here’s the source code of export_sessions.php
:
<?php
/**
* Retrieve session entry for given cookie.
* Based on https://drupal.stackexchange.com/a/231726/767
*/
use Drupal\Component\Utility\Crypt;
if (!empty($extra)) {
if (!str_starts_with($extra[0], '--cookie=')) {
die("Usage: drush scr export_sessions.php [-- --cookie=<value of SESSxxxx cookie>]\n");
}
else {
$cookie = trim(str_replace('--cookie=', '', $extra[0]));
$cookie = urldecode($cookie);
$sid = Crypt::hashBase64($cookie);
}
}
$connection = \Drupal::database();
if (isset($sid)) {
$query = $connection->query('SELECT * FROM {sessions} WHERE sid = :sid', [':sid' => $sid]);
}
else {
$query = $connection->query('SELECT * FROM {sessions}');
}
echo json_encode(array_map(function($session) {
preg_match_all('/_sf2_(\w+)\|/', $session->session, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
$session->sf2 = array_map(function($match, $index) use ($session, $matches) {
$offset = $match[0][1] + strlen($match[0][0]);
$length = $index + 1 < count($matches) ?
$matches[$index + 1][0][1] - $offset:
strlen($session->session) - 1 - $match[0][1];
return [
'name' => $match[1][0],
'value' => unserialize(substr($session->session, $offset, $length)),
];
}, $matches, array_keys($matches));
return $session;
}, $query->fetchAll()), JSON_PRETTY_PRINT) . "\n";
That’s it. Short and sweet!