Permalink
Browse files

Merge branch '1.6-tyzoid-password-hashes' into 1.6-next

Fixes #707.
Fixes #1069.
  • Loading branch information...
franzliedke committed Jan 4, 2019
2 parents d2fbaed + faef574 commit 1ad12d25a934266ff1afe70c65c550a10ea4a32c
Showing with 143 additions and 40 deletions.
  1. +50 −2 db_update.php
  2. +1 −1 include/common.php
  3. +71 −5 include/functions.php
  4. +3 −3 install.php
  5. +1 −0 lang/English/update.php
  6. +10 −22 login.php
  7. +5 −5 profile.php
  8. +2 −2 register.php
@@ -9,7 +9,7 @@
// The FluxBB version this script updates to
define('UPDATE_TO', '1.5.10');
define('UPDATE_TO_DB_REVISION', 21);
define('UPDATE_TO_DB_REVISION', 23);
define('UPDATE_TO_SI_REVISION', 2);
define('UPDATE_TO_PARSER_REVISION', 2);
@@ -117,6 +117,9 @@
if (version_compare($cur_version, '1.2', '<'))
error(sprintf($lang_update['Version mismatch error'], $db_name));
if (!isset($password_hash_cost))
error(sprintf($lang_update['Password cost missing error']));
// Do some DB type specific checks
$mysql = false;
switch ($db_type)
@@ -1710,7 +1713,7 @@ function _error_users($cur_user)
// Preparse signatures
case 'preparse_sigs':
$query_str = '?stage=rebuild_idx';
$query_str = '?stage=harden_passwords';
// If we don't need to parse the sigs, skip this stage
if (isset($pun_config['o_parser_revision']) && $pun_config['o_parser_revision'] >= UPDATE_TO_PARSER_REVISION)
@@ -1742,6 +1745,51 @@ function _error_users($cur_user)
break;
// Convert legacy passwords
case 'harden_passwords':
$query_str = '?stage=rebuild_idx';
// Make password field VARCHAR(255) to support password_hash
// 255 is recommended by the PHP manual: http://php.net/manual/en/function.password-hash.php
if ($start_at == 0)
$db->alter_field('users', 'password', 'VARCHAR(255)', false) or error('Unable to alter password field', __FILE__, __LINE__, $db->error());
// Fetch passwords in batches
$result = $db->query('SELECT * FROM '.$db->prefix.'users WHERE id>'.$start_at.' ORDER BY id ASC LIMIT '.PER_PAGE, false) or error('Unable to fetch user password hashes', __FILE__, __LINE__, $db->error());
while ($cur_user = $db->fetch_assoc($result))
{
$old_password = $cur_user['password'];
$remove_salt = !empty($cur_user['salt']);
if (strlen($old_password) == 32) // MD5 from 1.2
$new_password_hash = '#MD5#'.flux_password_hash($old_password);
else if ($remove_salt) // Salted SHA1 from 1.3
$new_password_hash = '#SHA1-S#'.$cur_user['salt'].'#'.flux_password_hash($old_password);
else if (strlen($old_password) == 40) // Unsalted SHA1 from 1.4
$new_password_hash = '#SHA1#'.flux_password_hash($old_password);
else
$new_password_hash = $old_password;
$db->query('UPDATE '.$db->prefix.'users SET '.($remove_salt ? 'salt=NULL,' : '').' password=\''.$db->escape($new_password_hash).'\' WHERE id='.$cur_user['id']) or error('Unable to save updated password', __FILE__, __LINE__, $db->error());
$end_at = $cur_user['id'];
}
if ($end_at > 0)
{
$result = $db->query('SELECT 1 FROM '.$db->prefix.'users WHERE id>'.$end_at.' ORDER BY id ASC LIMIT 1') or error('Unable to check for next row', __FILE__, __LINE__, $db->error());
if ($db->has_rows($result))
$query_str = '?stage=harden_passwords&start_at='.$end_at;
else
$db->drop_field('users', 'salt');
}
else
$db->drop_field('users', 'salt');
break;
// Rebuild the search index
case 'rebuild_idx':
$query_str = '?stage=finish';
@@ -12,7 +12,7 @@
// Define the version and database revision that this code was written for
define('FORUM_VERSION', '1.5.10');
define('FORUM_DB_REVISION', 21);
define('FORUM_DB_REVISION', 23);
define('FORUM_SI_REVISION', 2);
define('FORUM_PARSER_REVISION', 2);
@@ -32,7 +32,7 @@ function check_cookie(&$pun_user)
if (isset($cookie) && $cookie['user_id'] > 1 && $cookie['expiration_time'] > $now)
{
// If the cookie has been tampered with
$is_authorized = pun_hash_equals(forum_hmac($cookie['user_id'].'|'.$cookie['expiration_time'], $cookie_seed.'_cookie_hash'), $cookie['cookie_hash']);
$is_authorized = hash_equals(forum_hmac($cookie['user_id'].'|'.$cookie['expiration_time'], $cookie_seed.'_cookie_hash'), $cookie['cookie_hash']);
if (!$is_authorized)
{
$expire = $now + 31536000; // The cookie expires after a year
@@ -47,7 +47,7 @@ function check_cookie(&$pun_user)
$pun_user = $db->fetch_assoc($result);
// If user authorisation failed
$is_authorized = pun_hash_equals(forum_hmac($pun_user['password'], $cookie_seed.'_password_hash'), $cookie['password_hash']);
$is_authorized = hash_equals(forum_hmac($pun_user['password'], $cookie_seed.'_password_hash'), $cookie['password_hash']);
if (!isset($pun_user['id']) || !$is_authorized)
{
$expire = $now + 31536000; // The cookie expires after a year
@@ -154,8 +154,8 @@ function authenticate_user($user, $password, $password_is_hash = false)
$result = $db->query('SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE '.(is_int($user) ? 'u.id='.intval($user) : 'u.username=\''.$db->escape($user).'\'')) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
$pun_user = $db->fetch_assoc($result);
$is_password_authorized = pun_hash_equals($password, $pun_user['password']);
$is_hash_authorized = pun_hash_equals(pun_hash($password), $pun_user['password']);
$is_password_authorized = hash_equals($password, $pun_user['password']);
$is_hash_authorized = flux_password_verify($password, $pun_user['password']);
if (!isset($pun_user['id']) ||
($password_is_hash && !$is_password_authorized ||
@@ -1092,6 +1092,71 @@ function validate_redirect($redirect_url, $fallback_url)
}
//
// Compute the hash of a password
// using a secure password hashing algorithm, if available
// As of PHP 7.2, this is BLOWFISH.
//
function flux_password_hash($pass)
{
global $password_hash_cost;
return password_hash($pass, PASSWORD_DEFAULT, array('cost' => $password_hash_cost));
}
//
// Verify that $pass and $hash match
// This supports any password hashing algorithm
// used by flux_password_hash, but is also
// backwards-compatible with older versions of this software.
//
function flux_password_verify($pass, $hash)
{
if ($hash[0] == '#')
{
// MD5 from 1.2
if (substr($hash, 0, 5) == '#MD5#')
{
$pass = md5($pass);
$hash = substr($hash, 5);
}
// SHA1-With-Salt from 1.3
else if (substr($hash, 0, 8) == '#SHA1-S#')
{
preg_match('/^#SHA1-S#(.+)#(.+)$/', $hash, $matches);
list(, $salt, $hash) = $matches;
$pass = sha1($salt.sha1($pass));
}
// SHA1-Without-Salt from 1.4
else if (substr($hash, 0, 6) == '#SHA1#')
{
$pass = sha1($pass);
$hash = substr($hash, 6);
}
}
// Support current password standard
return password_verify($pass, $hash);
}
//
// Check if $hash is outdated and needs to be rehashed
//
function flux_password_needs_rehash($hash)
{
global $password_hash_cost;
// Check for legacy password (md5 or sha1 hash)
if ($hash[0] === '#')
return true;
// Check for out-of-date hash type or cost
return password_needs_rehash($hash, PASSWORD_DEFAULT, array('cost' => $password_hash_cost));
}
//
// Generate a random password of length $len
// Compatibility wrapper for random_key
@@ -1114,6 +1179,7 @@ function pun_hash($str)
//
// Compare two strings in constant time
// Inspired by WordPress
// @deprecated
//
function pun_hash_equals($a, $b)
{
@@ -1142,7 +1208,7 @@ function check_csrf($token)
{
global $lang_common;
$is_hash_authorized = pun_hash_equals($token, pun_csrf_token());
$is_hash_authorized = hash_equals($token, pun_csrf_token());
if (!isset($token) || !$is_hash_authorized)
message($lang_common['Bad csrf hash'], false, '404 Not Found');
@@ -9,7 +9,7 @@
// The FluxBB version this script installs
define('FORUM_VERSION', '1.5.10');
define('FORUM_DB_REVISION', 21);
define('FORUM_DB_REVISION', 23);
define('FORUM_SI_REVISION', 2);
define('FORUM_PARSER_REVISION', 2);
@@ -89,7 +89,7 @@ function generate_config_file()
{
global $db_type, $db_host, $db_name, $db_username, $db_password, $db_prefix, $cookie_name, $cookie_seed;
return '<?php'."\n\n".'$db_type = \''.$db_type."';\n".'$db_host = \''.$db_host."';\n".'$db_name = \''.addslashes($db_name)."';\n".'$db_username = \''.addslashes($db_username)."';\n".'$db_password = \''.addslashes($db_password)."';\n".'$db_prefix = \''.addslashes($db_prefix)."';\n".'$p_connect = false;'."\n\n".'$cookie_name = '."'".$cookie_name."';\n".'$cookie_domain = '."'';\n".'$cookie_path = '."'/';\n".'$cookie_secure = 0;'."\n".'$cookie_seed = \''.random_key(16, false, true)."';\n\ndefine('PUN', 1);\n";
return '<?php'."\n\n".'$db_type = \''.$db_type."';\n".'$db_host = \''.$db_host."';\n".'$db_name = \''.addslashes($db_name)."';\n".'$db_username = \''.addslashes($db_username)."';\n".'$db_password = \''.addslashes($db_password)."';\n".'$db_prefix = \''.addslashes($db_prefix)."';\n".'$p_connect = false;'."\n\n".'$cookie_name = '."'".$cookie_name."';\n".'$cookie_domain = '."'';\n".'$cookie_path = '."'/';\n".'$cookie_secure = 0;'."\n".'$cookie_seed = \''.random_key(16, false, true)."';\n\n".'$password_hash_cost = 10;'."\n\ndefine('PUN', 1);\n";
}
@@ -1492,7 +1492,7 @@ function process_form(the_form)
$db->query('INSERT INTO '.$db_prefix.'users (group_id, username, password, email) VALUES(3, \''.$db->escape($lang_install['Guest']).'\', \''.$db->escape($lang_install['Guest']).'\', \''.$db->escape($lang_install['Guest']).'\')')
or error('Unable to add guest user. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
$db->query('INSERT INTO '.$db_prefix.'users (group_id, username, password, email, language, style, num_posts, last_post, registered, registration_ip, last_visit) VALUES(1, \''.$db->escape($username).'\', \''.pun_hash($password1).'\', \''.$email.'\', \''.$db->escape($default_lang).'\', \''.$db->escape($default_style).'\', 1, '.$now.', '.$now.', \''.$db->escape(get_remote_address()).'\', '.$now.')')
$db->query('INSERT INTO '.$db_prefix.'users (group_id, username, password, email, language, style, num_posts, last_post, registered, registration_ip, last_visit) VALUES(1, \''.$db->escape($username).'\', \''.$db->escape(flux_password_hash($password1)).'\', \''.$email.'\', \''.$db->escape($default_lang).'\', \''.$db->escape($default_style).'\', 1, '.$now.', '.$now.', \''.$db->escape(get_remote_address()).'\', '.$now.')')
or error('Unable to add administrator user. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
// Enable/disable avatars depending on file_uploads setting in PHP configuration
@@ -18,6 +18,7 @@
'You are running error' => 'You are running %1$s version %2$s. FluxBB %3$s requires at least %1$s %4$s to run properly. You must upgrade your %1$s installation before you can continue.',
'Version mismatch error' => 'Version mismatch. The database \'%s\' doesn\'t seem to be running a FluxBB database schema supported by this update script.',
'Password cost missing error' => 'Update cannot proceed before you add a line containing <code>$password_hash_cost = 10;</code> to your config.php file in the FluxBB root directory',
'Invalid file error' => 'Invalid database file name. When using SQLite the database file name must be entered exactly as it appears in your \'%s\'',
'Invalid password error' => 'Invalid database password. To upgrade FluxBB you must enter your database password exactly as it appears in your \'%s\'',
'No password error' => 'No database password provided',
@@ -36,33 +36,21 @@
if (!empty($cur_user['password']))
{
$form_password_hash = pun_hash($form_password); // Will result in a SHA-1 hash
// Represents the hash of the user's password
// If it's transparently changed in this function,
// this allows the cookie token to reflect the new hash
$user_password = $cur_user['password'];
// If there is a salt in the database we have upgraded from 1.3-legacy though haven't yet logged in
if (!empty($cur_user['salt']))
if (flux_password_verify($form_password, $user_password))
{
$is_salt_authorized = pun_hash_equals(sha1($cur_user['salt'].sha1($form_password)), $cur_user['password']);
if ($is_salt_authorized) // 1.3 used sha1(salt.sha1(pass))
{
$authorized = true;
$authorized = true;
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$form_password_hash.'\', salt=NULL WHERE id='.$cur_user['id']) or error('Unable to update user password', __FILE__, __LINE__, $db->error());
}
}
// If the length isn't 40 then the password isn't using sha1, so it must be md5 from 1.2
else if (strlen($cur_user['password']) != 40)
{
$is_md5_authorized = pun_hash_equals(md5($form_password), $cur_user['password']);
if ($is_md5_authorized)
if (flux_password_needs_rehash($user_password))
{
$authorized = true;
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$form_password_hash.'\' WHERE id='.$cur_user['id']) or error('Unable to update user password', __FILE__, __LINE__, $db->error());
$user_password = flux_password_hash($form_password);
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$db->escape($user_password).'\' WHERE id='.$cur_user['id']) or error('Unable to update user password', __FILE__, __LINE__, $db->error());
}
}
// Otherwise we should have a normal sha1 password
else
$authorized = pun_hash_equals($cur_user['password'], $form_password_hash);
}
if (!$authorized)
@@ -89,7 +77,7 @@
$db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape(get_remote_address()).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
$expire = ($save_pass == '1') ? time() + 1209600 : time() + $pun_config['o_timeout_visit'];
pun_setcookie($cur_user['id'], $form_password_hash, $expire);
pun_setcookie($cur_user['id'], $user_password, $expire);
// Reset tracked topics
set_tracked_topics(null);
@@ -55,7 +55,7 @@
message($lang_profile['Pass key bad'].' <a href="mailto:'.pun_htmlspecialchars($pun_config['o_admin_email']).'">'.pun_htmlspecialchars($pun_config['o_admin_email']).'</a>.');
else
{
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$db->escape($cur_user['activate_string']).'\', activate_string=NULL, activate_key=NULL'.(!empty($cur_user['salt']) ? ', salt=NULL' : '').' WHERE id='.$id) or error('Unable to update password', __FILE__, __LINE__, $db->error());
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$db->escape($cur_user['activate_string']).'\', activate_string=NULL, activate_key=NULL WHERE id='.$id) or error('Unable to update password', __FILE__, __LINE__, $db->error());
message($lang_profile['Pass updated'], true);
}
@@ -102,16 +102,16 @@
{
$old_password_hash = pun_hash($old_password);
if ($cur_user['password'] == $old_password_hash || $pun_user['is_admmod'])
if (flux_password_verify($old_password, $cur_user['password']) || $pun_user['is_admmod'])
$authorized = true;
}
if (!$authorized)
message($lang_profile['Wrong pass']);
$new_password_hash = pun_hash($new_password1);
$new_password_hash = flux_password_hash($new_password1);
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$new_password_hash.'\''.(!empty($cur_user['salt']) ? ', salt=NULL' : '').' WHERE id='.$id) or error('Unable to update password', __FILE__, __LINE__, $db->error());
$db->query('UPDATE '.$db->prefix.'users SET password=\''.$db->escape($new_password_hash).'\' WHERE id='.$id) or error('Unable to update password', __FILE__, __LINE__, $db->error());
if ($pun_user['id'] == $id)
pun_setcookie($pun_user['id'], $new_password_hash, time() + $pun_config['o_timeout_visit']);
@@ -193,7 +193,7 @@
}
else if (isset($_POST['form_sent']))
{
if (pun_hash($_POST['req_password']) !== $pun_user['password'])
if (!flux_password_verify($_POST['req_password'], $pun_user['password']))
message($lang_profile['Wrong pass']);
// Make sure they got here from the site
@@ -157,10 +157,10 @@
$now = time();
$intial_group_id = ($pun_config['o_regs_verify'] == '0') ? $pun_config['o_default_user_group'] : PUN_UNVERIFIED;
$password_hash = pun_hash($password1);
$password_hash = flux_password_hash($password1);
// Add the user
$db->query('INSERT INTO '.$db->prefix.'users (username, group_id, password, email, email_setting, timezone, dst, language, style, registered, registration_ip, last_visit) VALUES(\''.$db->escape($username).'\', '.$intial_group_id.', \''.$password_hash.'\', \''.$db->escape($email1).'\', '.$email_setting.', '.$timezone.' , '.$dst.', \''.$db->escape($language).'\', \''.$pun_config['o_default_style'].'\', '.$now.', \''.$db->escape(get_remote_address()).'\', '.$now.')') or error('Unable to create user', __FILE__, __LINE__, $db->error());
$db->query('INSERT INTO '.$db->prefix.'users (username, group_id, password, email, email_setting, timezone, dst, language, style, registered, registration_ip, last_visit) VALUES(\''.$db->escape($username).'\', '.$intial_group_id.', \''.$db->escape($password_hash).'\', \''.$db->escape($email1).'\', '.$email_setting.', '.$timezone.' , '.$dst.', \''.$db->escape($language).'\', \''.$pun_config['o_default_style'].'\', '.$now.', \''.$db->escape(get_remote_address()).'\', '.$now.')') or error('Unable to create user', __FILE__, __LINE__, $db->error());
$new_uid = $db->insert_id();
if ($pun_config['o_regs_verify'] == '0')

0 comments on commit 1ad12d2

Please sign in to comment.