Skip to content
Permalink
Browse files Browse the repository at this point in the history
Make forgot password tokens use better random token #646
Fixes security issue discovered by Timothy D. Morgan <tim.morgan@owasp.org>
Forgotten password challenges were guessable based on users last login
and email address. Tokens are now generated based on a HMAC of login time
and email address using a salt and secret key specifically for these
tokens.
  • Loading branch information
rjmackay committed Nov 12, 2012
1 parent 6d41ec2 commit e8c7ecd
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 9 deletions.
13 changes: 4 additions & 9 deletions application/controllers/login.php
Expand Up @@ -320,13 +320,9 @@ public function index($user_id = 0)
else
{
// Reset locally

// Secret consists of email and the last_login field.
// So as soon as the user logs in again,
// the reset link expires automatically.
$secret = $auth->hash_password($user->email.$user->last_login);
$secret_link = url::site('login/index/'.$user->id.'/'.$secret.'?reset');
$email_sent = $this->_email_resetlink($post->resetemail,$user->name,$secret_link);
$secret = $user->forgot_password_token();
$secret_link = url::site('login/index/'.$user->id.'/'.urlencode($secret).'?reset');
$email_sent = $this->_email_resetlink($post->resetemail, $user->name, $secret_link);
}

if ($email_sent == TRUE)
Expand Down Expand Up @@ -870,8 +866,7 @@ private function _new_password($user_id = 0, $password, $token)
else
{
// Use Standard

if($auth->hash_password($user->email.$user->last_login, $auth->find_salt($token)) == $token)
if($user->check_forgot_password_token($token))
{
$user->password = $password;
$user->save();
Expand Down
9 changes: 9 additions & 0 deletions application/hooks/2_settings.php
Expand Up @@ -60,3 +60,12 @@
// Additional Mime Types (KMZ/KML)
Kohana::config_set('mimes.kml', array('text/xml'));
Kohana::config_set('mimes.kmz', array('text/xml'));

// Set 'settings.forgot_password_key' if not set already
if ( ! Kohana::config('settings.forgot_password_secret'))
{
$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+[]{};:,.?`~';
$key = text::random($pool, 64);
Settings_Model::save_setting('forgot_password_secret', $key);
Kohana::config_set('settings.forgot_password_secret', $key);
}
35 changes: 35 additions & 0 deletions application/models/user.php
Expand Up @@ -360,5 +360,40 @@ public function dashboard()
// Send anyone else to login
return 'login';
}

/**
* Get a new forgotten password challenge token for this user
* @param string $salt Optional salt for token generation (use this)
* @return string
*/
public function forgot_password_token()
{
return $this->_forgot_password_token();
}

/**
* Check to see if forgotten password token is valid
* @param string $token token to check
* @return boolean is token valid
**/
public function check_forgot_password_token($token)
{
$salt = substr($token, 0, 32);
return $this->_forgot_password_token($salt) == $token;
}

/**
* Generate a forgotten password challenge token for this user
* @param string $salt Optional salt for token generation (only use this for checking a token in URL)
* @return string token
*/
private function _forgot_password_token($salt = FALSE)
{
// Secret consists of email and the last_login field.
// So as soon as the user logs in again, the reset link expires automatically.
$salt = $salt ? $salt : text::random('alnum', 32); // Limited charset to keep it URL friendly
$key = Kohana::config('settings.forgot_password_secret');
return $salt . hash_hmac('sha1', $this->last_login . $this->email, $salt . $key);
}

} // End User_Model

0 comments on commit e8c7ecd

Please sign in to comment.