Store persistent-login keys as SHA-256 hashes.

Each device now has its own row. Editors must log in again once after
deploying.
This commit is contained in:
tim
2026-09-25 21:42:34 +02:00
parent dfa6d71198
commit ee20dc6eda
+40 -28
View File
@@ -25,35 +25,45 @@ if (isset($_SERVER['HTTPS'])) {
header( 'Pragma: no-cache' ); header( 'Pragma: no-cache' );
} }
/*
* Persistent logins: the cookie holds "username|key", and the database
* holds only a SHA-256 of the key, so a copy of the table cannot be used
* to log in. Each device has its own row.
*/
function persistent_cookie() {
$cookie_data = $_COOKIE['PersistentAuth'] ?? '';
if (!is_string($cookie_data))
$cookie_data = '';
return explode("|", $cookie_data, 2) + array('', '');
}
function persistent_key_hash($key) {
return hash('sha256', $key);
}
function check_persistent_login() { function check_persistent_login() {
if (!array_key_exists('PersistentAuth', $_COOKIE)) { if (!array_key_exists('PersistentAuth', $_COOKIE)) {
return false; return false;
} }
global $db, $is_admin; global $db, $is_admin;
$table = "persistent_sessions"; $table = "persistent_sessions";
list($username, $key) = persistent_cookie();
if ($username === '' || $key === '')
return false;
$key_hash = persistent_key_hash($key);
open_db('open'); open_db('open');
$query = "SELECT * FROM $table WHERE username = ? AND session = ? LIMIT 1";
$cookie_data = (string)$_COOKIE['PersistentAuth']; $getid = $db->execute_query($query, array($username, $key_hash)) or die("Query failed: ".$db->error);
list($username, $key) = explode("|", $cookie_data, 2) + array('', '');
$query = "SELECT * FROM $table WHERE username = ? LIMIT 1";
$getid = $db->execute_query($query, array($username)) or die("Query failed: ".$db->error);
$row = $getid->fetch_assoc(); $row = $getid->fetch_assoc();
open_db('close'); open_db('close');
if (isset($row)) { if (isset($row) && hash_equals((string)$row['session'], $key_hash)) {
// Set up session, and delete old cookie, and create new persistent cookie // Log in, then swap the used key for a new one
$stored_session = $row['session']; login_editor($username);
if ($stored_session == $key) { delete_persistent_login();
// login create_persistent_login($username);
login_editor($username); return true;
// delete cookie
delete_persistent_login();
// create new cookie
create_persistent_login($username);
return true;
}
return false;
} }
return false; return false;
} }
@@ -64,11 +74,11 @@ function create_persistent_login($username) {
$table = "persistent_sessions"; $table = "persistent_sessions";
open_db('open'); open_db('open');
$key = bin2hex(random_bytes(100)); $key = bin2hex(random_bytes(32));
// Add session key to database // Add the hash of the session key to the database
$query = "INSERT INTO $table (username, session) VALUES (?, ?)"; $query = "INSERT INTO $table (username, session) VALUES (?, ?)";
$db->execute_query($query, array($username, $key)) or die("Query failed: ".$db->error); $db->execute_query($query, array($username, persistent_key_hash($key))) or die("Query failed: ".$db->error);
open_db('close'); open_db('close');
// Create cookie with key // Create cookie with key
@@ -82,16 +92,18 @@ function delete_persistent_login() {
$table = "persistent_sessions"; $table = "persistent_sessions";
open_db('open'); open_db('open');
$cookie_data = (string)($_COOKIE['PersistentAuth'] ?? ''); list($username, $key) = persistent_cookie();
list($username, $key) = explode("|", $cookie_data, 2) + array('', '');
// Remove this session key from the database // Remove this session key from the database
$query = "DELETE FROM $table WHERE username = ? AND session = ?"; if ($username !== '' && $key !== '') {
$db->execute_query($query, array($username, $key)) or die("Query failed: ".$db->error); $query = "DELETE FROM $table WHERE username = ? AND session = ?";
$db->execute_query($query, array($username, persistent_key_hash($key))) or die("Query failed: ".$db->error);
}
// Reap old sessions // Reap old sessions, and keys stored before they were hashed
$query = "DELETE FROM $table " . $query = "DELETE FROM $table " .
" WHERE created < DATE_SUB(now(), INTERVAL 1 YEAR);"; " WHERE created < DATE_SUB(now(), INTERVAL 1 YEAR)" .
" OR CHAR_LENGTH(session) <> 64;";
$db->query($query) or die("Query failed: ".$db->error); $db->query($query) or die("Query failed: ".$db->error);
open_db('close'); open_db('close');