Rework autologin to use a token approach

The prior code stored encrypted user data in the cookie that was then checked. This new approach is cleaner, as it only stores a token, and it does not use problematic crypto functions deprecated in PHP 7.2
This commit is contained in:
onli 2018-02-12 11:55:05 +01:00
parent 4a43761e72
commit 52a41b37d5

View File

@ -341,7 +341,8 @@ function serendipity_load_configuration($author = null) {
function serendipity_logout() {
$_SESSION['serendipityAuthedUser'] = false;
serendipity_session_destroy();
serendipity_deleteCookie('author_information');
serendipity_deleteCookie('author_username');
serendipity_deleteCookie('author_autologintoken');
serendipity_deleteCookie('author_token');
}
@ -382,28 +383,22 @@ function serendipity_login($use_external = true) {
if (serendipity_authenticate_author($serendipity['POST']['user'], $serendipity['POST']['pass'], false, $use_external)) {
if (empty($serendipity['POST']['auto'])) {
serendipity_deleteCookie('author_information');
serendipity_deleteCookie('author_information_iv');
return false;
} else {
serendipity_issueAutologin(
array('username' => $serendipity['POST']['user'],
'password' => $serendipity['POST']['pass']
)
);
serendipity_issueAutologin($serendipity['POST']['user']);
return true;
}
// Now try login via COOKIE data
} elseif (isset($serendipity['COOKIE']['author_information'])) {
$cookie = serendipity_checkAutologin($serendipity['COOKIE']['author_information'], $serendipity['COOKIE']['author_information_iv']);
} elseif (isset($serendipity['COOKIE']['author_username'])) {
$user = $serendipity['COOKIE']['author_username'];
$valid_logintoken = serendipity_checkAutologin($user);
$data = array('ext' => $use_external, 'mode' => 1, 'user' => $cookie['username'], 'pass' => $cookie['password']);
serendipity_plugin_api::hook_event('backend_loginfail', $data);
if (is_array($cookie) && serendipity_authenticate_author($cookie['username'], $cookie['password'], false, $use_external)) {
if ($valid_logintoken === true) {
serendipity_load_userdata($user);
return true;
} else {
serendipity_deleteCookie('author_information');
serendipity_deleteCookie('author_information_iv');
serendipity_deleteCookie('author_username');
serendipity_deleteCookie('author_autologintoken');
return false;
}
}
@ -413,70 +408,80 @@ function serendipity_login($use_external = true) {
}
/**
* Issue a new auto login cookie
* @param array The input data
* Issue a new auto login cookie. For that we store in the options table (name, value, okey) the values (autologin_$username, a random token, the current time).
* @param String The username
*/
function serendipity_issueAutologin($array) {
function serendipity_issueAutologin($user) {
global $serendipity;
$package = serialize($array);
if (function_exists('mcrypt_encrypt')) {
// Secure the package data when being stored inside the Database
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC), MCRYPT_RAND);
$key = base64_encode($iv);
$package = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $package, MCRYPT_MODE_CBC, $iv);
serendipity_setCookie('author_information_iv', $key);
try {
// TODO: Add https://github.com/paragonie/random_compat for PHP < 7.0
$random_string = random_bytes(32);
} catch (TypeError $e) {
// Well, it's an integer, so this IS unexpected.
die("An unexpected error has occurred");
} catch (Error $e) {
// This is also unexpected because 32 is a reasonable integer.
die("An unexpected error has occurred");
} catch (Exception $e) {
// If you get this message, the CSPRNG failed hard.
die("Could not generate a random string. Is our OS secure?");
}
$package = base64_encode($package);
$rnd = md5(uniqid(time(), true) . $_SERVER['REMOTE_ADDR']);
$rnd = bin2hex($random_string);
// Delete possible current cookie. Also delete any autologin keys that smell like 3-week-old, dead fish.
if (stristr($serendipity['dbType'], 'sqlite')) {
$cast = "name";
$cast = "okey";
} else {
// Adds explicits casting for mysql, postgresql and others.
$cast = "cast(name as integer)";
$cast = "cast(okey as integer)";
}
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}options
WHERE okey = 'l_" . serendipity_db_escape_string($serendipity['COOKIE']['author_information']) . "'
OR (okey LIKE 'l_%' AND $cast < " . (time() - 1814400) . ")");
WHERE name = 'autologin_" . serendipity_db_escape_string($user) . "'
OR (name LIKE 'autologin_%' AND $cast < " . (time() - 1814400) . ")");
// Issue new autologin cookie
serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}options (name, value, okey) VALUES ('" . time() . "', '" . serendipity_db_escape_string($package) . "', 'l_" . $rnd . "')");
serendipity_setCookie('author_information', $rnd);
serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}options (name, value, okey) VALUES ('autologin_" . serendipity_db_escape_string($user) . "', '" . $rnd . "', '" . time() . "')");
serendipity_setCookie('author_autologintoken', $rnd);
serendipity_setCookie('author_username', $user);
}
/**
* Checks a new auto login cookie
* @param array The input data
* Checks a new auto login cookie, by seeing whether the token stored in the cookie is the same as the one stored in the database
* @param String The username
* @returns bool true if valid, false if not
*/
function serendipity_checkAutologin($ident, $iv) {
function serendipity_checkAutologin($user) {
global $serendipity;
// Fetch login data from DB
$autologin =& serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}options WHERE okey = 'l_" . serendipity_db_escape_string($ident) . "' LIMIT 1", true, 'assoc');
if (!is_array($autologin)) {
if (stristr($serendipity['dbType'], 'sqlite')) {
$cast = "okey";
} else {
// Adds explicits casting for mysql, postgresql and others.
$cast = "cast(okey as integer)";
}
// Fetch autologin data from DB
$autologin_stored = serendipity_db_query("SELECT name, value, okey FROM {$serendipity['dbPrefix']}options WHERE name = 'autologin_" . serendipity_db_escape_string($user) . "' AND $cast > " . (time() - 1814400) . " LIMIT 1", true, 'assoc');
if (!is_array($autologin_stored)) {
return false;
}
if (function_exists('mcrypt_decrypt') && !empty($iv)) {
$key = $iv;
$iv = base64_decode($iv);
$cookie = unserialize(mcrypt_decrypt(MCRYPT_BLOWFISH, $key, base64_decode($autologin['value']), MCRYPT_MODE_CBC, $iv));
} else {
$cookie = unserialize(base64_decode($autologin['value']));
if ($serendipity['COOKIE']['author_autologintoken'] !== $autologin_stored['value']) {
return false;
}
if ($autologin['name'] < (time()-86400)) {
if ($autologin_stored['okey'] < (time()-86400)) {
// Issued autologin cookie has been issued more than 1 day ago. Re-Issue new cookie, invalidate old one to prevent abuse
if ($serendipity['expose_s9y']) serendipity_header('X-ReIssue-Cookie: +' . (time() - $autologin['name']) . 's');
serendipity_issueAutologin($cookie);
if ($serendipity['expose_s9y']) serendipity_header('X-ReIssue-Cookie: +' . (time() - $autologin_stored['okey']) . 's');
serendipity_issueAutologin($user);
}
return $cookie;
return true;
}
/**
@ -590,18 +595,9 @@ function serendipity_authenticate_author($username = '', $password = '', $is_has
$_SESSION['serendipityPassword'] = $serendipity['serendipityPassword'] = $password;
}
$_SESSION['serendipityUser'] = $serendipity['serendipityUser'] = $username;
$_SESSION['serendipityRealname'] = $serendipity['serendipityRealname'] = $row['realname'];
$_SESSION['serendipityEmail'] = $serendipity['serendipityEmail'] = $row['email'];
$_SESSION['serendipityAuthorid'] = $serendipity['authorid'] = $row['authorid'];
$_SESSION['serendipityUserlevel'] = $serendipity['serendipityUserlevel'] = $row['userlevel'];
$_SESSION['serendipityAuthedUser'] = $serendipity['serendipityAuthedUser'] = true;
$_SESSION['serendipityRightPublish'] = $serendipity['serendipityRightPublish'] = $row['right_publish'];
$_SESSION['serendipityHashType'] = $serendipity['serendipityHashType'] = $row['hashtype'];
return serendipity_load_userdata($username);
serendipity_load_configuration($serendipity['authorid']);
serendipity_setCookie('userDefLang', $serendipity['lang'], false);
return true;
}
}
}
@ -621,6 +617,33 @@ function serendipity_authenticate_author($username = '', $password = '', $is_has
return false;
}
function serendipity_load_userdata($username) {
global $serendipity;
$query = "SELECT DISTINCT
email, password, realname, authorid, userlevel, right_publish, hashtype
FROM
{$serendipity['dbPrefix']}authors
WHERE
username = '" . serendipity_db_escape_string($username) . "'";
$rows = serendipity_db_query($query, false, 'assoc');
$row = $rows[0];
$_SESSION['serendipityUser'] = $serendipity['serendipityUser'] = $username;
$_SESSION['serendipityRealname'] = $serendipity['serendipityRealname'] = $row['realname'];
$_SESSION['serendipityEmail'] = $serendipity['serendipityEmail'] = $row['email'];
$_SESSION['serendipityAuthorid'] = $serendipity['authorid'] = $row['authorid'];
$_SESSION['serendipityUserlevel'] = $serendipity['serendipityUserlevel'] = $row['userlevel'];
$_SESSION['serendipityAuthedUser'] = $serendipity['serendipityAuthedUser'] = true;
$_SESSION['serendipityRightPublish'] = $serendipity['serendipityRightPublish'] = $row['right_publish'];
$_SESSION['serendipityHashType'] = $serendipity['serendipityHashType'] = $row['hashtype'];
serendipity_load_configuration($serendipity['authorid']);
serendipity_setCookie('userDefLang', $serendipity['lang'], false);
return true;
}
/**
* Check if a user is logged in
*