diff --git a/session.lib.php b/session.lib.php index 8f30675..b5b18d2 100644 --- a/session.lib.php +++ b/session.lib.php @@ -25,35 +25,45 @@ if (isset($_SERVER['HTTPS'])) { 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() { if (!array_key_exists('PersistentAuth', $_COOKIE)) { return false; } global $db, $is_admin; $table = "persistent_sessions"; + list($username, $key) = persistent_cookie(); + if ($username === '' || $key === '') + return false; + $key_hash = persistent_key_hash($key); + open_db('open'); - - $cookie_data = (string)$_COOKIE['PersistentAuth']; - 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); + $query = "SELECT * FROM $table WHERE username = ? AND session = ? LIMIT 1"; + $getid = $db->execute_query($query, array($username, $key_hash)) or die("Query failed: ".$db->error); $row = $getid->fetch_assoc(); open_db('close'); - if (isset($row)) { - // Set up session, and delete old cookie, and create new persistent cookie - $stored_session = $row['session']; - if ($stored_session == $key) { - // login - login_editor($username); - // delete cookie - delete_persistent_login(); - // create new cookie - create_persistent_login($username); - return true; - } - return false; + if (isset($row) && hash_equals((string)$row['session'], $key_hash)) { + // Log in, then swap the used key for a new one + login_editor($username); + delete_persistent_login(); + create_persistent_login($username); + return true; } return false; } @@ -64,11 +74,11 @@ function create_persistent_login($username) { $table = "persistent_sessions"; 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 (?, ?)"; - $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'); // Create cookie with key @@ -82,16 +92,18 @@ function delete_persistent_login() { $table = "persistent_sessions"; open_db('open'); - $cookie_data = (string)($_COOKIE['PersistentAuth'] ?? ''); - list($username, $key) = explode("|", $cookie_data, 2) + array('', ''); + list($username, $key) = persistent_cookie(); // Remove this session key from the database - $query = "DELETE FROM $table WHERE username = ? AND session = ?"; - $db->execute_query($query, array($username, $key)) or die("Query failed: ".$db->error); + if ($username !== '' && $key !== '') { + $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 " . - " 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); open_db('close');