From 5809474cf1e9f29654b27d7a65b42e683f400622 Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Thu, 8 Mar 2018 15:37:58 -0600 Subject: [PATCH 1/9] web: Add password compatability library (MIT License) from https://github.com/ircmaxell/password_compat --- html/inc/password.php | 318 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 html/inc/password.php diff --git a/html/inc/password.php b/html/inc/password.php new file mode 100644 index 00000000000..a2962c8f6f1 --- /dev/null +++ b/html/inc/password.php @@ -0,0 +1,318 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +namespace { + + if (!defined('PASSWORD_BCRYPT')) { + /** + * PHPUnit Process isolation caches constants, but not function declarations. + * So we need to check if the constants are defined separately from + * the functions to enable supporting process isolation in userland + * code. + */ + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + define('PASSWORD_BCRYPT_DEFAULT_COST', 10); + } + + if (!function_exists('password_hash')) { + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (is_null($password) || is_int($password)) { + $password = (string) $password; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + $resultLength = 0; + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = PASSWORD_BCRYPT_DEFAULT_COST; + if (isset($options['cost'])) { + $cost = (int) $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + // The expected length of the final crypt() output + $resultLength = 60; + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + $salt_req_encoding = false; + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt_req_encoding = true; + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $strong = false; + $buffer = openssl_random_pseudo_bytes($raw_salt_len, $strong); + if ($buffer && $strong) { + $buffer_valid = true; + } + } + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $file = fopen('/dev/urandom', 'r'); + $read = 0; + $local_buffer = ''; + while ($read < $raw_salt_len) { + $local_buffer .= fread($file, $raw_salt_len - $read); + $read = PasswordCompat\binary\_strlen($local_buffer); + } + fclose($file); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + $buffer = str_pad($buffer, $raw_salt_len, "\0") ^ str_pad($local_buffer, $raw_salt_len, "\0"); + } + if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { + $buffer_length = PasswordCompat\binary\_strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $buffer_length) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = $buffer; + $salt_req_encoding = true; + } + if ($salt_req_encoding) { + // encode string with the Base64 variant used by crypt + $base64_digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = + './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + } + $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => PASSWORD_BCRYPT_DEFAULT_COST, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] !== (int) $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? (int) $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST; + if ($cost !== $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } + } + +} + +namespace PasswordCompat\binary { + + if (!function_exists('PasswordCompat\\binary\\_strlen')) { + + /** + * Count the number of bytes in a string + * + * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. + * In this case, strlen() will count the number of *characters* based on the internal encoding. A + * sequence of bytes might be regarded as a single multibyte character. + * + * @param string $binary_string The input string + * + * @internal + * @return int The number of bytes + */ + function _strlen($binary_string) { + if (function_exists('mb_strlen')) { + return mb_strlen($binary_string, '8bit'); + } + return strlen($binary_string); + } + + /** + * Get a substring based on byte limits + * + * @see _strlen() + * + * @param string $binary_string The input string + * @param int $start + * @param int $length + * + * @internal + * @return string The substring + */ + function _substr($binary_string, $start, $length) { + if (function_exists('mb_substr')) { + return mb_substr($binary_string, $start, $length, '8bit'); + } + return substr($binary_string, $start, $length); + } + + /** + * Check if current PHP version is compatible with the library + * + * @return boolean the check result + */ + function check() { + static $pass = NULL; + + if (is_null($pass)) { + if (function_exists('crypt')) { + $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'; + $test = crypt("password", $hash); + $pass = $test == $hash; + } else { + $pass = false; + } + } + return $pass; + } + + } +} + From 4b147e6cb20c9b65ae7ebb657107aa39990e0874 Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Thu, 8 Mar 2018 15:53:39 -0600 Subject: [PATCH 2/9] web: initial changes for improving password hashing (join, change email, change password, login) --- html/inc/user_util.inc | 6 ++++-- html/user/edit_email_action.php | 8 +++++--- html/user/edit_passwd_action.php | 4 +++- html/user/login_action.php | 12 ++++++++++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/html/inc/user_util.inc b/html/inc/user_util.inc index ce24e2645c4..67ad0b73516 100644 --- a/html/inc/user_util.inc +++ b/html/inc/user_util.inc @@ -23,6 +23,7 @@ include_once("../inc/boinc_db.inc"); include_once("../inc/util.inc"); include_once("../inc/email.inc"); include_once("../inc/recaptchalib.php"); +require_once("../inc/password.php"); function is_banned_email_addr($email_addr) { global $banned_email_domains; @@ -68,12 +69,13 @@ function make_user( $email_addr = BoincDb::escape_string($email_addr); $name = sanitize_tags($name); $name = BoincDb::escape_string($name); - $passwd_hash = BoincDb::escape_string($passwd_hash); + $database_passwd_hash = password_hash( $passwd_hash, PASSWORD_DEFAULT); + $database_passwd_hash = BoincDb::escape_string($database_passwd_hash); $country = BoincDb::escape_string($country); $postal_code = sanitize_tags(BoincDb::escape_string($postal_code)); - $uid = BoincUser::insert("(create_time, email_addr, name, authenticator, country, postal_code, total_credit, expavg_credit, expavg_time, project_prefs, teamid, venue, send_email, show_hosts, posts, seti_id, seti_nresults, seti_last_result_time, seti_total_cpu, has_profile, cross_project_id, passwd_hash, email_validated, donated) values($now, '$email_addr', '$name', '$authenticator', '$country', '$postal_code', 0, 0, unix_timestamp(), '$project_prefs', $teamid, '', 1, 1, 0, 0, 0, 0, 0, 0, '$cross_project_id', '$passwd_hash', 0, 0)"); + $uid = BoincUser::insert("(create_time, email_addr, name, authenticator, country, postal_code, total_credit, expavg_credit, expavg_time, project_prefs, teamid, venue, send_email, show_hosts, posts, seti_id, seti_nresults, seti_last_result_time, seti_total_cpu, has_profile, cross_project_id, passwd_hash, email_validated, donated) values($now, '$email_addr', '$name', '$authenticator', '$country', '$postal_code', 0, 0, unix_timestamp(), '$project_prefs', $teamid, '', 1, 1, 0, 0, 0, 0, 0, 0, '$cross_project_id', '$database_passwd_hash', 0, 0)"); if (!$uid) { return null; diff --git a/html/user/edit_email_action.php b/html/user/edit_email_action.php index 1359fff9fa0..7445bba4302 100644 --- a/html/user/edit_email_action.php +++ b/html/user/edit_email_action.php @@ -20,6 +20,7 @@ require_once("../inc/util.inc"); require_once("../inc/email.inc"); require_once("../inc/user_util.inc"); +require_once("../inc/password.php"); check_get_args(array()); @@ -46,17 +47,18 @@ // deal with the case where user hasn't set passwd // (i.e. passwd is account key) // - if ($passwd_hash != $user->passwd_hash) { + if ($passwd_hash != $user->passwd_hash && !password_verify($passwd_hash,$user->passwd_hash)) { $passwd = $user->authenticator; $passwd_hash = md5($passwd.$user->email_addr); } - if ($passwd_hash != $user->passwd_hash) { + if ($passwd_hash != $user->passwd_hash && !password_verify($passwd_hash,$user->passwd_hash)) { echo tra("Invalid password."); } else { $passwd_hash = md5($passwd.$email_addr); + $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT ); $email_addr = BoincDb::escape_string($email_addr); $result = $user->update( - "email_addr='$email_addr', passwd_hash='$passwd_hash', email_validated=0" + "email_addr='$email_addr', passwd_hash='$database_passwd_hash', email_validated=0" ); if ($result) { echo tra("The email address of your account is now %1.", $email_addr); diff --git a/html/user/edit_passwd_action.php b/html/user/edit_passwd_action.php index f6ad3fa1764..f06d3a222cb 100644 --- a/html/user/edit_passwd_action.php +++ b/html/user/edit_passwd_action.php @@ -19,6 +19,7 @@ require_once("../inc/boinc_db.inc"); require_once("../inc/util.inc"); require_once("../inc/user.inc"); +require_once("../inc/password.php"); check_get_args(array()); @@ -45,7 +46,8 @@ } $passwd_hash = md5($passwd.$user->email_addr); -$result = $user->update("passwd_hash='$passwd_hash'"); +$database_passwd_hash = password_hash( $passwd_hash, PASSWORD_DEFAULT); +$result = $user->update("passwd_hash='$database_passwd_hash'"); if (!$result) { error_page(tra("We can't update your password due to a database problem. Please try again later.")); } diff --git a/html/user/login_action.php b/html/user/login_action.php index 9d859f23d14..2f05f82b37b 100644 --- a/html/user/login_action.php +++ b/html/user/login_action.php @@ -28,6 +28,7 @@ require_once("../inc/email.inc"); require_once("../inc/user.inc"); require_once("../inc/ldap.inc"); +require_once("../inc/password.php"); check_get_args(array("id", "t", "h", "key")); @@ -49,9 +50,16 @@ function login_with_email($email_addr, $passwd, $next_url, $perm) { error_page("This account has been administratively disabled."); } // allow authenticator as password - if ($passwd != $user->authenticator) { + if ($passwd != $user->authenticator ) { $passwd_hash = md5($passwd.$email_addr); - if ($passwd_hash != $user->passwd_hash) { + if ( $passwd_hash == $user->passwd_hash || password_verify($passwd_hash,$user->passwd_hash) ) { + // on valid login, rehash password in order to upgrade hash overtime + // as the defaults change. Also converts users passwords from md5 if required + $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); + $result = $user->update( + "passwd_hash='$database_passwd_hash'" + ); + } else { sleep(LOGIN_FAIL_SLEEP_SEC); page_head("Password incorrect"); echo "The password you entered is incorrect. Please go back and try again.\n"; From 09a36668032b91125be31aa6f9320d838bf4b740 Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Fri, 9 Mar 2018 09:18:01 -0600 Subject: [PATCH 3/9] web: Only rehash password on login when needed --- html/user/login_action.php | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/html/user/login_action.php b/html/user/login_action.php index 2f05f82b37b..4aa8e5c6ce0 100644 --- a/html/user/login_action.php +++ b/html/user/login_action.php @@ -32,6 +32,13 @@ check_get_args(array("id", "t", "h", "key")); +function do_passwd_rehash($user,$passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); + $result = $user->update( + "passwd_hash='$database_passwd_hash'" + ); +} + // login with email addr / passwd // function login_with_email($email_addr, $passwd, $next_url, $perm) { @@ -52,14 +59,17 @@ function login_with_email($email_addr, $passwd, $next_url, $perm) { // allow authenticator as password if ($passwd != $user->authenticator ) { $passwd_hash = md5($passwd.$email_addr); - if ( $passwd_hash == $user->passwd_hash || password_verify($passwd_hash,$user->passwd_hash) ) { - // on valid login, rehash password in order to upgrade hash overtime - // as the defaults change. Also converts users passwords from md5 if required - $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); - $result = $user->update( - "passwd_hash='$database_passwd_hash'" - ); - } else { + if ( password_verify($passwd_hash,$user->passwd_hash) ) { + // on valid login, rehash password if necessary to upgrade hash overtime + // as the defaults change. + if ( password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT) ) { + do_passwd_rehash($user,$passwd_hash); + } + } else if ( $passwd_hash == $user->passwd_hash ) { + // if password is the legacy md5 hash, then rehash to update to + // a more secure hash + do_passwd_rehash($user,$passwd_hash); + } else { sleep(LOGIN_FAIL_SLEEP_SEC); page_head("Password incorrect"); echo "The password you entered is incorrect. Please go back and try again.\n"; From cfa5cd67239cfb0fbe961b6b49f3a3f83f8cb49b Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Fri, 9 Mar 2018 13:51:41 -0600 Subject: [PATCH 4/9] web: Fix autofocus and tab order for the login form --- html/inc/account.inc | 9 +++++---- html/inc/bootstrap.inc | 12 ++++++------ html/user/login_action.php | 2 +- html/user/login_form.php | 6 ------ 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/html/inc/account.inc b/html/inc/account.inc index f11c011fc58..21584c12921 100644 --- a/html/inc/account.inc +++ b/html/inc/account.inc @@ -123,19 +123,20 @@ function login_form($next_url) { } else { $x = tra("Email address:"); } - form_input_text($x, "email_addr"); + form_input_text($x, "email_addr", '', 'text', $attrs='autofocus tabindex="1"'); form_input_text( tra("Password:").'
' . tra("forgot password?") . "", "passwd", "", "password", - 'id="passwd"', + 'id="passwd" tabindex="2"', passwd_visible_checkbox("passwd") ); form_checkboxes(tra("Stay logged in"), - array(array("stay_logged_in", "", true)) + array(array("stay_logged_in", "", true)), + 'tabindex="3"' ); - form_submit("Log in"); + form_submit("Log in", 'tabindex="4"'); form_end(); } diff --git a/html/inc/bootstrap.inc b/html/inc/bootstrap.inc index c1385300e13..1c913c13c91 100644 --- a/html/inc/bootstrap.inc +++ b/html/inc/bootstrap.inc @@ -387,7 +387,7 @@ function form_select_multiple($label, $name, $items, $flags) { // $items is list of (name, label, checked) // -function form_checkboxes($label, $items) { +function form_checkboxes($label, $items, $attrs='') { echo sprintf('
@@ -402,9 +402,9 @@ function form_checkboxes($label, $items) { } else { echo "
\n"; } - echo sprintf(' %s + echo sprintf(' %s ', - $i[0], $i[2]?"checked":"", $i[1] + $attrs, $i[0], $i[2]?"checked":"", $i[1] ); } echo '
@@ -456,11 +456,11 @@ function form_general($label, $item) { '; } -function form_submit($text) { +function form_submit($text, $attrs='') { form_general( "", - sprintf('', - $text + sprintf('', + $attrs, $text ) ); } diff --git a/html/user/login_action.php b/html/user/login_action.php index 4aa8e5c6ce0..0348f5d95d6 100644 --- a/html/user/login_action.php +++ b/html/user/login_action.php @@ -69,7 +69,7 @@ function login_with_email($email_addr, $passwd, $next_url, $perm) { // if password is the legacy md5 hash, then rehash to update to // a more secure hash do_passwd_rehash($user,$passwd_hash); - } else { + } else { sleep(LOGIN_FAIL_SLEEP_SEC); page_head("Password incorrect"); echo "The password you entered is incorrect. Please go back and try again.\n"; diff --git a/html/user/login_form.php b/html/user/login_form.php index cb01dd5de71..86323d720b6 100644 --- a/html/user/login_form.php +++ b/html/user/login_form.php @@ -59,11 +59,5 @@ echo tra("or %1 create an account %2.", "",""); } -echo " - -"; - page_tail(); ?> From 2f299967aae1aa7892cab4a0dee4dfaa2a82571b Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Fri, 9 Mar 2018 15:05:16 -0600 Subject: [PATCH 5/9] web: changes for improving password hashing (admin, web_rpcs) --- html/ops/login_action.php | 20 +++++++++++++++++++- html/user/am_set_info.php | 4 +++- html/user/create_account.php | 3 ++- html/user/lookup_account.php | 30 +++++++++++++++++++++++++----- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/html/ops/login_action.php b/html/ops/login_action.php index 9f8aed12f27..1fc82687a1b 100644 --- a/html/ops/login_action.php +++ b/html/ops/login_action.php @@ -22,6 +22,14 @@ require_once("../inc/util_ops.inc"); require_once("../inc/email.inc"); require_once("../inc/user.inc"); +require_once("../inc/password.php"); + +function do_passwd_rehash($user,$passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); + $result = $user->update( + "passwd_hash='$database_passwd_hash'" + ); +} // check for email/password case // @@ -34,7 +42,17 @@ admin_error_page("No account found with email address $email_addr"); } $passwd_hash = md5($passwd.$email_addr); - if ($passwd_hash != $user->passwd_hash) { + if ( password_verify($passwd_hash,$user->passwd_hash) ) { + // on valid login, rehash password if necessary to upgrade hash overtime + // as the defaults change. + if ( password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT) ) { + do_passwd_rehash($user,$passwd_hash); + } + } else if ( $passwd_hash == $user->passwd_hash ) { + // if password is the legacy md5 hash, then rehash to update to + // a more secure hash + do_passwd_rehash($user,$passwd_hash); + } else { admin_error_page("Login failed"); } $authenticator = $user->authenticator; diff --git a/html/user/am_set_info.php b/html/user/am_set_info.php index 5d4651e504d..602ab414608 100644 --- a/html/user/am_set_info.php +++ b/html/user/am_set_info.php @@ -20,6 +20,7 @@ require_once("../inc/xml.inc"); require_once("../inc/team.inc"); require_once("../inc/email.inc"); +require_once("../inc/password.php"); // do a very cursory check that the given text is valid; // for now, just make sure it has the given start and end tags, @@ -176,7 +177,8 @@ function success($x) { $query .= " email_addr='$email_addr', "; } if ($password_hash) { - $query .= " passwd_hash='$password_hash', "; + $database_passwd_hash = password_hash($password_hash , PASSWORD_DEFAULT); + $query .= " passwd_hash='$database_passwd_hash', "; } if (strlen($query)) { diff --git a/html/user/create_account.php b/html/user/create_account.php index 28fb9da31f2..65fcecb5420 100644 --- a/html/user/create_account.php +++ b/html/user/create_account.php @@ -24,6 +24,7 @@ require_once("../inc/xml.inc"); require_once("../inc/user_util.inc"); require_once("../inc/team.inc"); +require_once("../inc/password.php"); xml_header(); @@ -69,7 +70,7 @@ $user = BoincUser::lookup_email_addr($email_addr); if ($user) { - if ($user->passwd_hash != $passwd_hash) { + if ($user->passwd_hash != $passwd_hash && !password_verify($passwd_hash,$user->passwd_hash)) { xml_error(ERR_DB_NOT_UNIQUE); } else { $authenticator = $user->authenticator; diff --git a/html/user/lookup_account.php b/html/user/lookup_account.php index eb52987b01b..9c32c076b01 100644 --- a/html/user/lookup_account.php +++ b/html/user/lookup_account.php @@ -23,6 +23,14 @@ require_once("../inc/email.inc"); require_once("../inc/xml.inc"); require_once("../inc/ldap.inc"); +require_once("../inc/password.php"); + +function do_passwd_rehash($user,$passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); + $result = $user->update( + "passwd_hash='$database_passwd_hash'" + ); +} xml_header(); $retval = db_init_xml(); @@ -72,16 +80,28 @@ // if no password set, set password to account key // if (!strlen($user->passwd_hash)) { - $user->passwd_hash = $auth_hash; + $user->passwd_hash = password_hash($auth_hash , PASSWORD_DEFAULT); $user->update("passwd_hash='$user->passwd_hash'"); } - - // if the given password hash matches (auth+email), accept it - // - if ($user->passwd_hash != $passwd_hash && $auth_hash != $passwd_hash) { + + if ( password_verify($passwd_hash,$user->passwd_hash) ) { + // on valid login, rehash password if necessary to upgrade hash overtime + // as the defaults change. + if ( password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT) ) { + do_passwd_rehash($user,$passwd_hash); + } + } else if ( $passwd_hash == $user->passwd_hash ) { + // if password is the legacy md5 hash, then rehash to update to + // a more secure hash + do_passwd_rehash($user,$passwd_hash); + } else if ( $auth_hash == $passwd_hash ) { + // if the passed hash matches the auth hash, then allow it + } else { + // if none of the above match, the password is invalid sleep(LOGIN_FAIL_SLEEP_SEC); xml_error(ERR_BAD_PASSWD); } + } echo "\n"; echo "$user->authenticator\n"; From ea6f2c35ac081ff67b55c6d0bfbd886526025f5d Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Tue, 13 Mar 2018 16:23:27 -0500 Subject: [PATCH 6/9] web: Once hashed a password does not need to be escaped when inserting it into the database --- html/inc/user_util.inc | 1 - 1 file changed, 1 deletion(-) diff --git a/html/inc/user_util.inc b/html/inc/user_util.inc index 67ad0b73516..dcdb6ac3c51 100644 --- a/html/inc/user_util.inc +++ b/html/inc/user_util.inc @@ -70,7 +70,6 @@ function make_user( $name = sanitize_tags($name); $name = BoincDb::escape_string($name); $database_passwd_hash = password_hash( $passwd_hash, PASSWORD_DEFAULT); - $database_passwd_hash = BoincDb::escape_string($database_passwd_hash); $country = BoincDb::escape_string($country); $postal_code = sanitize_tags(BoincDb::escape_string($postal_code)); From 1ceb1e3a24a445d1df5e19d6893c5297321b3605 Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Tue, 13 Mar 2018 16:49:14 -0500 Subject: [PATCH 7/9] web: Fix style incompatibilities --- html/inc/user_util.inc | 2 +- html/ops/login_action.php | 18 ++++++++---------- html/user/am_set_info.php | 2 +- html/user/create_account.php | 2 +- html/user/edit_email_action.php | 6 +++--- html/user/edit_passwd_action.php | 4 ++-- html/user/login_action.php | 20 +++++++++----------- html/user/lookup_account.php | 24 +++++++++++------------- 8 files changed, 36 insertions(+), 42 deletions(-) diff --git a/html/inc/user_util.inc b/html/inc/user_util.inc index dcdb6ac3c51..59f9f2ff2ed 100644 --- a/html/inc/user_util.inc +++ b/html/inc/user_util.inc @@ -69,7 +69,7 @@ function make_user( $email_addr = BoincDb::escape_string($email_addr); $name = sanitize_tags($name); $name = BoincDb::escape_string($name); - $database_passwd_hash = password_hash( $passwd_hash, PASSWORD_DEFAULT); + $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); $country = BoincDb::escape_string($country); $postal_code = sanitize_tags(BoincDb::escape_string($postal_code)); diff --git a/html/ops/login_action.php b/html/ops/login_action.php index 1fc82687a1b..e18fa47c096 100644 --- a/html/ops/login_action.php +++ b/html/ops/login_action.php @@ -24,11 +24,9 @@ require_once("../inc/user.inc"); require_once("../inc/password.php"); -function do_passwd_rehash($user,$passwd_hash) { - $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); - $result = $user->update( - "passwd_hash='$database_passwd_hash'" - ); +function do_passwd_rehash($user, $passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); + $result = $user->update(" passwd_hash='$database_passwd_hash' "); } // check for email/password case @@ -42,16 +40,16 @@ function do_passwd_rehash($user,$passwd_hash) { admin_error_page("No account found with email address $email_addr"); } $passwd_hash = md5($passwd.$email_addr); - if ( password_verify($passwd_hash,$user->passwd_hash) ) { + if (password_verify($passwd_hash, $user->passwd_hash)) { // on valid login, rehash password if necessary to upgrade hash overtime // as the defaults change. - if ( password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT) ) { - do_passwd_rehash($user,$passwd_hash); + if (password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT)) { + do_passwd_rehash($user, $passwd_hash); } - } else if ( $passwd_hash == $user->passwd_hash ) { + } else if ($passwd_hash == $user->passwd_hash) { // if password is the legacy md5 hash, then rehash to update to // a more secure hash - do_passwd_rehash($user,$passwd_hash); + do_passwd_rehash($user, $passwd_hash); } else { admin_error_page("Login failed"); } diff --git a/html/user/am_set_info.php b/html/user/am_set_info.php index 602ab414608..0c28762be33 100644 --- a/html/user/am_set_info.php +++ b/html/user/am_set_info.php @@ -177,7 +177,7 @@ function success($x) { $query .= " email_addr='$email_addr', "; } if ($password_hash) { - $database_passwd_hash = password_hash($password_hash , PASSWORD_DEFAULT); + $database_passwd_hash = password_hash($password_hash, PASSWORD_DEFAULT); $query .= " passwd_hash='$database_passwd_hash', "; } diff --git a/html/user/create_account.php b/html/user/create_account.php index 65fcecb5420..3ba6aaab670 100644 --- a/html/user/create_account.php +++ b/html/user/create_account.php @@ -70,7 +70,7 @@ $user = BoincUser::lookup_email_addr($email_addr); if ($user) { - if ($user->passwd_hash != $passwd_hash && !password_verify($passwd_hash,$user->passwd_hash)) { + if ($user->passwd_hash != $passwd_hash && !password_verify($passwd_hash, $user->passwd_hash)) { xml_error(ERR_DB_NOT_UNIQUE); } else { $authenticator = $user->authenticator; diff --git a/html/user/edit_email_action.php b/html/user/edit_email_action.php index 7445bba4302..1c706a1e85d 100644 --- a/html/user/edit_email_action.php +++ b/html/user/edit_email_action.php @@ -47,15 +47,15 @@ // deal with the case where user hasn't set passwd // (i.e. passwd is account key) // - if ($passwd_hash != $user->passwd_hash && !password_verify($passwd_hash,$user->passwd_hash)) { + if ($passwd_hash != $user->passwd_hash && !password_verify($passwd_hash, $user->passwd_hash)) { $passwd = $user->authenticator; $passwd_hash = md5($passwd.$user->email_addr); } - if ($passwd_hash != $user->passwd_hash && !password_verify($passwd_hash,$user->passwd_hash)) { + if ($passwd_hash != $user->passwd_hash && !password_verify($passwd_hash, $user->passwd_hash)) { echo tra("Invalid password."); } else { $passwd_hash = md5($passwd.$email_addr); - $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT ); + $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); $email_addr = BoincDb::escape_string($email_addr); $result = $user->update( "email_addr='$email_addr', passwd_hash='$database_passwd_hash', email_validated=0" diff --git a/html/user/edit_passwd_action.php b/html/user/edit_passwd_action.php index f06d3a222cb..ddae5ac65ed 100644 --- a/html/user/edit_passwd_action.php +++ b/html/user/edit_passwd_action.php @@ -46,8 +46,8 @@ } $passwd_hash = md5($passwd.$user->email_addr); -$database_passwd_hash = password_hash( $passwd_hash, PASSWORD_DEFAULT); -$result = $user->update("passwd_hash='$database_passwd_hash'"); +$database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); +$result = $user->update(" passwd_hash='$database_passwd_hash' "); if (!$result) { error_page(tra("We can't update your password due to a database problem. Please try again later.")); } diff --git a/html/user/login_action.php b/html/user/login_action.php index 0348f5d95d6..3f2a1457d3f 100644 --- a/html/user/login_action.php +++ b/html/user/login_action.php @@ -32,11 +32,9 @@ check_get_args(array("id", "t", "h", "key")); -function do_passwd_rehash($user,$passwd_hash) { - $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); - $result = $user->update( - "passwd_hash='$database_passwd_hash'" - ); +function do_passwd_rehash($user, $passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); + $result = $user->update(" passwd_hash='$database_passwd_hash' "); } // login with email addr / passwd @@ -57,18 +55,18 @@ function login_with_email($email_addr, $passwd, $next_url, $perm) { error_page("This account has been administratively disabled."); } // allow authenticator as password - if ($passwd != $user->authenticator ) { + if ($passwd != $user->authenticator) { $passwd_hash = md5($passwd.$email_addr); - if ( password_verify($passwd_hash,$user->passwd_hash) ) { + if (password_verify($passwd_hash, $user->passwd_hash)) { // on valid login, rehash password if necessary to upgrade hash overtime // as the defaults change. - if ( password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT) ) { - do_passwd_rehash($user,$passwd_hash); + if (password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT)) { + do_passwd_rehash($user, $passwd_hash); } - } else if ( $passwd_hash == $user->passwd_hash ) { + } else if ($passwd_hash == $user->passwd_hash) { // if password is the legacy md5 hash, then rehash to update to // a more secure hash - do_passwd_rehash($user,$passwd_hash); + do_passwd_rehash($user, $passwd_hash); } else { sleep(LOGIN_FAIL_SLEEP_SEC); page_head("Password incorrect"); diff --git a/html/user/lookup_account.php b/html/user/lookup_account.php index 9c32c076b01..d003a8be36d 100644 --- a/html/user/lookup_account.php +++ b/html/user/lookup_account.php @@ -25,11 +25,9 @@ require_once("../inc/ldap.inc"); require_once("../inc/password.php"); -function do_passwd_rehash($user,$passwd_hash) { - $database_passwd_hash = password_hash($passwd_hash , PASSWORD_DEFAULT); - $result = $user->update( - "passwd_hash='$database_passwd_hash'" - ); +function do_passwd_rehash($user, $passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); + $result = $user->update(" passwd_hash='$database_passwd_hash' "); } xml_header(); @@ -80,21 +78,21 @@ function do_passwd_rehash($user,$passwd_hash) { // if no password set, set password to account key // if (!strlen($user->passwd_hash)) { - $user->passwd_hash = password_hash($auth_hash , PASSWORD_DEFAULT); - $user->update("passwd_hash='$user->passwd_hash'"); + $user->passwd_hash = password_hash($auth_hash, PASSWORD_DEFAULT); + $user->update(" passwd_hash='$user->passwd_hash' "); } - if ( password_verify($passwd_hash,$user->passwd_hash) ) { + if (password_verify($passwd_hash, $user->passwd_hash)) { // on valid login, rehash password if necessary to upgrade hash overtime // as the defaults change. - if ( password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT) ) { - do_passwd_rehash($user,$passwd_hash); + if (password_needs_rehash($user->passwd_hash, PASSWORD_DEFAULT)) { + do_passwd_rehash($user, $passwd_hash); } - } else if ( $passwd_hash == $user->passwd_hash ) { + } else if ($passwd_hash == $user->passwd_hash) { // if password is the legacy md5 hash, then rehash to update to // a more secure hash - do_passwd_rehash($user,$passwd_hash); - } else if ( $auth_hash == $passwd_hash ) { + do_passwd_rehash($user, $passwd_hash); + } else if ($auth_hash == $passwd_hash) { // if the passed hash matches the auth hash, then allow it } else { // if none of the above match, the password is invalid From 333e5c49eb6e6664312d23001dd213bc0454c4c6 Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Wed, 4 Apr 2018 13:46:16 -0500 Subject: [PATCH 8/9] Add the password compatability library to the create project setup scripts --- html/inc/{password.php => password_compat/password.inc} | 0 py/Boinc/setup_project.py | 2 ++ 2 files changed, 2 insertions(+) rename html/inc/{password.php => password_compat/password.inc} (100%) diff --git a/html/inc/password.php b/html/inc/password_compat/password.inc similarity index 100% rename from html/inc/password.php rename to html/inc/password_compat/password.inc diff --git a/py/Boinc/setup_project.py b/py/Boinc/setup_project.py index ca95662bb70..dc3239bfd4c 100644 --- a/py/Boinc/setup_project.py +++ b/py/Boinc/setup_project.py @@ -264,6 +264,7 @@ def mkdir2(d): 'html', 'html/cache', 'html/inc', + 'html/inc/password_compat', 'html/inc/ReCaptcha', 'html/inc/ReCaptcha/RequestMethod', 'html/languages', @@ -316,6 +317,7 @@ def dir(*dirs): if install_web_files: install_glob(srcdir('html/inc/*.inc'), dir('html/inc/')) install_glob(srcdir('html/inc/*.php'), dir('html/inc/')) + install_glob(srcdir('html/inc/password_compat/*.inc'), dir('html/inc/password_compat/')) install_glob(srcdir('html/inc/ReCaptcha/*.php'), dir('html/inc/ReCaptcha/')) install_glob(srcdir('html/inc/ReCaptcha/RequestMethod/*.php'), dir('html/inc/ReCaptcha/RequestMethod')) install_glob(srcdir('html/inc/*.dat'), dir('html/inc/')) From 78f96d25f369e579e30d1ae8e46f3259fe2105dd Mon Sep 17 00:00:00 2001 From: Kevin Reed Date: Wed, 4 Apr 2018 13:47:26 -0500 Subject: [PATCH 9/9] web: refactor password hashing changes to move compatibility library as a .inc file and to move common functions into user_util.inc --- html/inc/password_compat/password.inc | 1 + html/inc/user_util.inc | 7 ++++++- html/ops/login_action.php | 8 ++------ html/user/am_set_info.php | 2 +- html/user/create_account.php | 2 +- html/user/edit_email_action.php | 2 +- html/user/edit_passwd_action.php | 2 +- html/user/login_action.php | 8 ++------ html/user/lookup_account.php | 8 ++------ 9 files changed, 17 insertions(+), 23 deletions(-) diff --git a/html/inc/password_compat/password.inc b/html/inc/password_compat/password.inc index a2962c8f6f1..2b9a56ec25e 100644 --- a/html/inc/password_compat/password.inc +++ b/html/inc/password_compat/password.inc @@ -5,6 +5,7 @@ * @author Anthony Ferrara * @license http://www.opensource.org/licenses/mit-license.html MIT License * @copyright 2012 The Authors + * @link https://github.com/ircmaxell/password_compat */ namespace { diff --git a/html/inc/user_util.inc b/html/inc/user_util.inc index 5fec1bfe972..b28198de16e 100644 --- a/html/inc/user_util.inc +++ b/html/inc/user_util.inc @@ -23,7 +23,12 @@ include_once("../inc/boinc_db.inc"); include_once("../inc/util.inc"); include_once("../inc/email.inc"); include_once("../inc/recaptchalib.php"); -require_once("../inc/password.php"); +require_once("../inc/password_compat/password.inc"); + +function do_passwd_rehash($user, $passwd_hash) { + $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); + $result = $user->update(" passwd_hash='$database_passwd_hash' "); +} function is_banned_email_addr($email_addr) { global $banned_email_domains; diff --git a/html/ops/login_action.php b/html/ops/login_action.php index e18fa47c096..fa7b67f112c 100644 --- a/html/ops/login_action.php +++ b/html/ops/login_action.php @@ -22,12 +22,8 @@ require_once("../inc/util_ops.inc"); require_once("../inc/email.inc"); require_once("../inc/user.inc"); -require_once("../inc/password.php"); - -function do_passwd_rehash($user, $passwd_hash) { - $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); - $result = $user->update(" passwd_hash='$database_passwd_hash' "); -} +require_once("../inc/user_util.inc"); +require_once("../inc/password_compat/password.inc"); // check for email/password case // diff --git a/html/user/am_set_info.php b/html/user/am_set_info.php index 0c28762be33..cf6f3232f3a 100644 --- a/html/user/am_set_info.php +++ b/html/user/am_set_info.php @@ -20,7 +20,7 @@ require_once("../inc/xml.inc"); require_once("../inc/team.inc"); require_once("../inc/email.inc"); -require_once("../inc/password.php"); +require_once("../inc/password_compat/password.inc"); // do a very cursory check that the given text is valid; // for now, just make sure it has the given start and end tags, diff --git a/html/user/create_account.php b/html/user/create_account.php index 3ba6aaab670..19d422af325 100644 --- a/html/user/create_account.php +++ b/html/user/create_account.php @@ -24,7 +24,7 @@ require_once("../inc/xml.inc"); require_once("../inc/user_util.inc"); require_once("../inc/team.inc"); -require_once("../inc/password.php"); +require_once("../inc/password_compat/password.inc"); xml_header(); diff --git a/html/user/edit_email_action.php b/html/user/edit_email_action.php index 1c706a1e85d..b0ef5f4481a 100644 --- a/html/user/edit_email_action.php +++ b/html/user/edit_email_action.php @@ -20,7 +20,7 @@ require_once("../inc/util.inc"); require_once("../inc/email.inc"); require_once("../inc/user_util.inc"); -require_once("../inc/password.php"); +require_once("../inc/password_compat/password.inc"); check_get_args(array()); diff --git a/html/user/edit_passwd_action.php b/html/user/edit_passwd_action.php index 85b7d87b134..736a5a0e51c 100644 --- a/html/user/edit_passwd_action.php +++ b/html/user/edit_passwd_action.php @@ -19,7 +19,7 @@ require_once("../inc/boinc_db.inc"); require_once("../inc/util.inc"); require_once("../inc/user.inc"); -require_once("../inc/password.php"); +require_once("../inc/password_compat/password.inc"); check_get_args(array()); diff --git a/html/user/login_action.php b/html/user/login_action.php index 3f2a1457d3f..5d973f25536 100644 --- a/html/user/login_action.php +++ b/html/user/login_action.php @@ -28,15 +28,11 @@ require_once("../inc/email.inc"); require_once("../inc/user.inc"); require_once("../inc/ldap.inc"); -require_once("../inc/password.php"); +require_once("../inc/user_util.inc"); +require_once("../inc/password_compat/password.inc"); check_get_args(array("id", "t", "h", "key")); -function do_passwd_rehash($user, $passwd_hash) { - $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); - $result = $user->update(" passwd_hash='$database_passwd_hash' "); -} - // login with email addr / passwd // function login_with_email($email_addr, $passwd, $next_url, $perm) { diff --git a/html/user/lookup_account.php b/html/user/lookup_account.php index d003a8be36d..6b72978886e 100644 --- a/html/user/lookup_account.php +++ b/html/user/lookup_account.php @@ -23,12 +23,8 @@ require_once("../inc/email.inc"); require_once("../inc/xml.inc"); require_once("../inc/ldap.inc"); -require_once("../inc/password.php"); - -function do_passwd_rehash($user, $passwd_hash) { - $database_passwd_hash = password_hash($passwd_hash, PASSWORD_DEFAULT); - $result = $user->update(" passwd_hash='$database_passwd_hash' "); -} +require_once("../inc/user_util.inc"); +require_once("../inc/password_compat/password.inc"); xml_header(); $retval = db_init_xml();