Permalink
Browse files

Implement more secure password hashing functionality via password_hash

Automatically rehash if cost or algorithm changes.
  • Loading branch information...
tyzoid authored and franzliedke committed Jan 2, 2019
1 parent d2fbaed commit f20c700b603ee8bd41c745d5e6b95cf901521e49
Showing with 119 additions and 34 deletions.
  1. +5 −1 db_update.php
  2. +1 −1 include/common.php
  3. +90 −1 include/functions.php
  4. +3 −3 install.php
  5. +14 −22 login.php
  6. +4 −4 profile.php
  7. +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', 22);
define('UPDATE_TO_SI_REVISION', 2);
define('UPDATE_TO_PARSER_REVISION', 2);
@@ -677,6 +677,10 @@ function convert_table_utf8($table, $callback, $old_charset, $key = null, $start
$db->alter_field('users', 'msn', 'VARCHAR(80)', true) or error('Unable to alter msn field', __FILE__, __LINE__, $db->error());
$db->alter_field('users', 'activate_string', 'VARCHAR(80)', true) or error('Unable to alter activate_string field', __FILE__, __LINE__, $db->error());
// 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
$db->alter_field('users', 'password', 'VARCHAR(255)', false) or error('Unable to alter password field', __FILE__, __LINE__, $db->error());
// Make all IP fields VARCHAR(39) to support IPv6
$db->alter_field('posts', 'poster_ip', 'VARCHAR(39)', true) or error('Unable to alter poster_ip field', __FILE__, __LINE__, $db->error());
$db->alter_field('users', 'registration_ip', 'VARCHAR(39)', false, '0.0.0.0') or error('Unable to alter registration_ip field', __FILE__, __LINE__, $db->error());
@@ -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', 22);
define('FORUM_SI_REVISION', 2);
define('FORUM_PARSER_REVISION', 2);
@@ -155,7 +155,7 @@ function authenticate_user($user, $password, $password_is_hash = false)
$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_hash_authorized = pun_password_verify($password, $pun_user['password']);
if (!isset($pun_user['id']) ||
($password_is_hash && !$is_password_authorized ||
@@ -1092,6 +1092,95 @@ 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.
// This function will fall back to unsecure defaults if
// password_hash does not exist (requires >=PHP5.5)
//
function pun_password_hash($pass)
{
global $password_hash_cost;
$cost = $password_hash_cost;
if (empty($cost))
$cost = 10;
if (function_exists('password_hash'))
return password_hash($pass, PASSWORD_DEFAULT, array('cost' => $cost));
else
return pun_hash($pass);
}
//
// Verify that $pass and $hash match
// This supports any password hashing algorithm
// used by pun_password_hash
//
function pun_password_verify($pass, $hash)
{
if (!empty($hash) && $hash[0] !== '$')
return pun_hash_equals(pun_hash($pass), $hash);
else
return password_verify($pass, $hash);
}
//
// Verify that $pass and $hash match
// This supports any password hashing algorithm
// used by pun_password_hash, but is also
// backwards-compatable with older versions of this software.
//
function pun_password_verify_legacy($pass, $hash, $salt = null)
{
// MD5 from 1.2
if (strlen($hash) < 40)
return pun_hash_equals(md5($pass), $hash);
// SHA1-With-Salt from 1.3
if (!empty($salt))
return pun_hash_equals(sha1($salt . sha1($pass)), $hash);
// SHA1-Without-Salt from 1.4
if (strlen($hash) == 40)
return pun_hash_equals(sha1($pass), $hash);
// Support current password standard
return pun_password_verify($pass, $hash);
}
//
// Check if $hash is outdated and needs to be rehashed
//
function pun_password_needs_rehash($hash)
{
global $password_hash_cost;
// Determine appropriate cost
$cost = $password_hash_cost;
if (empty($cost))
$cost = 10;
// Check for legacy md5 hash
if (strlen($hash) < 40)
return true;
// Check for legacy sha1 hash. Note: legacy sha1 is used
// if password_hash is not available
if (function_exists('password_hash') && strlen($hash) == 40)
return true;
// Check for out-of-date hash type or cost
if (function_exists('password_needs_rehash'))
return password_needs_rehash($hash, PASSWORD_DEFAULT, array('cost' => $cost));
return false;
}
//
// Generate a random password of length $len
// Compatibility wrapper for random_key
@@ -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', 22);
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(pun_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
@@ -36,33 +36,25 @@
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 (pun_password_verify_legacy($form_password, $user_password, $cur_user['salt']))
{
$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)
{
$authorized = true;
$remove_salt = !empty($cur_user['salt']);
$rehash = $remove_salt || pun_password_needs_rehash($user_password);
$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());
if ($rehash)
{
$user_password = pun_password_hash($form_password);
$salt_sql = ($remove_salt ? 'salt=NULL,' : '');
$db->query('UPDATE '.$db->prefix.'users SET '.$salt_sql.' 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 +81,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);
@@ -102,16 +102,16 @@
{
$old_password_hash = pun_hash($old_password);
if ($cur_user['password'] == $old_password_hash || $pun_user['is_admmod'])
if (pun_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 = pun_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).'\''.(!empty($cur_user['salt']) ? ', salt=NULL' : '').' 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 (!pun_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 = pun_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 f20c700

Please sign in to comment.