Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

15278 Hash password reset token securely #18022

Closed
wants to merge 1 commit into from
Closed

15278 Hash password reset token securely #18022

wants to merge 1 commit into from

Conversation

mwithheld
Copy link
Contributor

Description:

This proposed fix for #15278 hashes the password reset token securely, and returns the same results every time the hash is created with the same data. I chose the ssh3-512 algorithm as it seems to be a decent balance between a secure hash and the time it takes to generate the hash.

/**
* Hash algorithm used to create the password reset token.
*/
const TOKEN_HASH_ALGO = 'sha3-512';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi would maybe need to check if this hash algo is available and if not fall back to a different one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to PHP hash_algos(), support for sha3-512 was added in PHP7.1, and the minimum requirement for Matomo is PHP7.2.

@Findus23
Copy link
Member

I'm still not totally happy with using a hash algorithm that is optimized to be calculated as fast as possible on modern hardware instead of one intended for cryptographic use and optimized to be rather slow like bcrypt or argon2. But calling them directly outside of password_hash() doesn't seem to be intended in PHP.

Also this is a bit out of the scope of this issue, but I got the feeling that a password reset token is more a topic of cryptographically signing the data that needs verification instead of hashing it.

@Findus23
Copy link
Member

Okay, I looked into the password reset process a bit more and am now even more confused as to why we need this hash at all.

I always assumed the password reset was not stored server side at all, but Matomo actually stores the information about the password server side:

/**
* Gets password hash stored in password reset info.
*
* @param string $login The user login to check for.
* @return string|false The hashed password or false if no reset info exists.
*/
private function getPasswordToResetTo($login)
{
$optionName = self::getPasswordResetInfoOptionName($login);
$optionValue = Option::get($optionName);
$optionValue = json_decode($optionValue, $isAssoc = true);
return $optionValue;
}

But if we are already doing this, then we could simplify the password reset by a lot without any insecure hashes, weird shuffeling around of characters and many ways to mess this up in a security critical way.
Instead we could just generate a long random string from a cryptographically secure source, store it in the DB (with metadata) as done now and send it to the user. And on clicking on the reset link we can compare them if they are the same and accept it if it is.
Maybe I am missing something important, but this seems more secure and a lot harder to mess up to me.

@sgiehl
Copy link
Member

sgiehl commented Sep 17, 2021

@Findus23 The token isn't stored. It's tried to be regenerated based on the given user and the possible reset time range. See

public function isTokenValid($token, $user, $keySuffix)
{
$now = time();
// token valid for 24 hrs (give or take, due to the coarse granularity in our strftime format string)
for ($i = 0; $i <= 24; $i++) {
$generatedToken = $this->generatePasswordResetToken($user, $keySuffix, $now + $i * 60 * 60);
if ($generatedToken === $token) {
return true;
}
}
// fails if token is invalid, expired, password already changed, other user information has changed, ...
return false;
}

@Findus23
Copy link
Member

@sgiehl But the keySuffix is stored, which is where the actual randomness comes from (I think all other things passed to the hash can be guessed by an attacker)

@sgiehl
Copy link
Member

sgiehl commented Sep 20, 2021

I'm not sure if changing the hash algorithm for that specific part has any advantages or not. Common::hash, which was used before, actually uses the hash_algorithm configured in config. This defaults to whirlpool. Not sure if changing that to something more modern would break anything, but maybe it might be better than changing it only for the password resetter maybe?

@mwithheld
Copy link
Contributor Author

@sgiehl that circles back to my first implementation attempt. The thing with the config option is it's for a slightly different purpose, and could (and really should) be set to an algorithm that has varying results for the same input.

@sgiehl
Copy link
Member

sgiehl commented Sep 24, 2021

I guess currently is expected behavior that Common::hash shouldn't have varying results. So it might be safe to change the default hash algo in configuration maybe. But besides that we could also completely refactor the password resetter. Not sure if would have any disadvantages / security concerns to store the hash in the database, so there is no need to actually regenerate it 🤔

@tsteur
Copy link
Member

tsteur commented Sep 26, 2021

Seems we're already storing parts of the token in savePasswordResetInfo (keySuffix). Could we instead store the full token in there? Not in plain text but eg use password_hash and password_verify for the token ? It's actually not ideal that keySuffix is stored in plain text.

@tsteur
Copy link
Member

tsteur commented Sep 26, 2021

In my opinion it be fine if any sent password reset link before the Matomo update to this version will break if they try to click after the Matomo update. AKA any link generated from keySuffix will no longer work. But since links are only valid for 24 hours + they are clicked usually right after requesting the link it should be fine.

@github-actions
Copy link
Contributor

If you don't want this PR to be closed automatically in 28 days then you need to assign the label 'Do not close'.

@github-actions github-actions bot added the Stale The label used by the Close Stale Issues action label Oct 11, 2021
@github-actions
Copy link
Contributor

This PR was last updated more than one month ago, maybe it's time to close it. Please check if there is anything we still can do or close this PR. ping @matomo-org/core-reviewers

@github-actions github-actions bot added the Stale for long The label used by the Close Stale Issues action label Nov 23, 2021
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Stale for long The label used by the Close Stale Issues action Stale The label used by the Close Stale Issues action
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants