Changeset 29111 for trunk


Ignore:
Timestamp:
Jul 28, 2014, 9:27:50 PM (10 years ago)
Author:
plg
Message:

bug 3050: increase security on reset password algorithm.

  • reset key has a 1-hour life
  • reset key is automatically deleted once used
  • reset key is stored as a hash

Thank you effigies for code suggestions

Location:
trunk
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/functions_user.inc.php

    r29075 r29111  
    14671467    .','.pwg_db_get_recent_period_expression(1,$user['last_photo_date']).')';
    14681468}
    1469 
    1470 /**
    1471  * Returns a unique activation key.
    1472  *
    1473  * @return string
    1474  */
    1475 function get_user_activation_key()
    1476 {
    1477   while (true)
    1478   {
    1479     $key = generate_key(20);
    1480     $query = '
    1481 SELECT COUNT(*)
    1482   FROM '.USER_INFOS_TABLE.'
    1483   WHERE activation_key = \''.$key.'\'
    1484 ;';
    1485     list($count) = pwg_db_fetch_row(pwg_query($query));
    1486     if (0 == $count)
    1487     {
    1488       return $key;
    1489     }
    1490   }
    1491 }
    1492 
    14931469?>
  • trunk/password.php

    r28587 r29111  
    9090  }
    9191
    92   if (empty($userdata['activation_key']))
    93   {
    94     $activation_key = get_user_activation_key();
    95 
    96     single_update(
    97       USER_INFOS_TABLE,
    98       array('activation_key' => $activation_key),
    99       array('user_id' => $user_id)
    100       );
    101 
    102     $userdata['activation_key'] = $activation_key;
    103   }
     92  $activation_key = generate_key(20);
     93
     94  list($expire) = pwg_db_fetch_row(pwg_query('SELECT ADDDATE(NOW(), INTERVAL 1 HOUR)'));
     95
     96  single_update(
     97    USER_INFOS_TABLE,
     98    array(
     99      'activation_key' => pwg_password_hash($activation_key),
     100      'activation_key_expire' => $expire,
     101      ),
     102    array('user_id' => $user_id)
     103    );
     104 
     105  $userdata['activation_key'] = $activation_key;
    104106
    105107  set_make_full_url();
     
    113115  $message.= "\r\n\r\n";
    114116  $message.= l10n('To reset your password, visit the following address:') . "\r\n";
    115   $message.= get_gallery_home_url().'/password.php?key='.$userdata['activation_key']."\r\n\r\n";
     117  $message.= get_gallery_home_url().'/password.php?key='.$activation_key.'-'.urlencode($userdata['email']);
     118  $message.= "\r\n\r\n";
    116119  $message.= l10n('If this was a mistake, just ignore this email and nothing will happen.')."\r\n";
    117120
     
    144147 * @return mixed (user_id if OK, false otherwise)
    145148 */
    146 function check_password_reset_key($key)
    147 {
    148   global $page;
    149  
     149function check_password_reset_key($reset_key)
     150{
     151  global $page, $conf;
     152
     153  list($key, $email) = explode('-', $reset_key, 2);
     154
    150155  if (!preg_match('/^[a-z0-9]{20}$/i', $key))
    151156  {
     
    154159  }
    155160
     161  $user_ids = array();
     162 
     163  $query = '
     164SELECT
     165  '.$conf['user_fields']['id'].' AS id
     166  FROM '.USERS_TABLE.'
     167  WHERE '.$conf['user_fields']['email'].' = \''.pwg_db_real_escape_string($email).'\'
     168;';
     169  $user_ids = query2array($query, null, 'id');
     170
     171  if (count($user_ids) == 0)
     172  {
     173    $page['errors'][] = l10n('Invalid username or email');
     174    return false;
     175  }
     176
     177  $user_id = null;
     178 
    156179  $query = '
    157180SELECT
    158181    user_id,
    159     status
     182    status,
     183    activation_key,
     184    activation_key_expire,
     185    NOW() AS dbnow
    160186  FROM '.USER_INFOS_TABLE.'
    161   WHERE activation_key = \''.$key.'\'
     187  WHERE user_id IN ('.implode(',', $user_ids).')
    162188;';
    163189  $result = pwg_query($query);
    164 
    165   if (pwg_db_num_rows($result) == 0)
     190  while ($row = pwg_db_fetch_assoc($result))
     191  {
     192    if (pwg_password_verify($key, $row['activation_key']))
     193    {
     194      if (strtotime($row['dbnow']) > strtotime($row['activation_key_expire']))
     195      {
     196        // key has expired
     197        $page['errors'][] = l10n('Invalid key');
     198        return false;
     199      }
     200
     201      if (is_a_guest($row['status']) or is_generic($row['status']))
     202      {
     203        $page['errors'][] = l10n('Password reset is not allowed for this user');
     204        return false;
     205      }
     206
     207      $user_id = $row['user_id'];
     208    }
     209  }
     210
     211  if (empty($user_id))
    166212  {
    167213    $page['errors'][] = l10n('Invalid key');
     
    169215  }
    170216 
    171   $userdata = pwg_db_fetch_assoc($result);
    172 
    173   if (is_a_guest($userdata['status']) or is_generic($userdata['status']))
    174   {
    175     $page['errors'][] = l10n('Password reset is not allowed for this user');
    176     return false;
    177   }
    178 
    179   return $userdata['user_id'];
     217  return $user_id;
    180218}
    181219
     
    188226function reset_password()
    189227{
    190   global $page, $user, $conf;
     228  global $page, $conf;
    191229
    192230  if ($_POST['use_new_pwd'] != $_POST['passwordConf'])
     
    196234  }
    197235
    198   if (isset($_GET['key']))
    199   {
    200     $user_id = check_password_reset_key($_GET['key']);
    201     if (!is_numeric($user_id))
    202     {
    203       $page['errors'][] = l10n('Invalid key');
    204       return false;
    205     }
    206   }
    207   else
    208   {
    209     // we check the currently logged in user
    210     if (is_a_guest() or is_generic())
    211     {
    212       $page['errors'][] = l10n('Password reset is not allowed for this user');
    213       return false;
    214     }
    215 
    216     $user_id = $user['id'];
     236  if (!isset($_GET['key']))
     237  {
     238    $page['errors'][] = l10n('Invalid key');
     239  }
     240 
     241  $user_id = check_password_reset_key($_GET['key']);
     242 
     243  if (!is_numeric($user_id))
     244  {
     245    return false;
    217246  }
    218247   
     
    223252    );
    224253
     254  single_update(
     255    USER_INFOS_TABLE,
     256    array(
     257      'activation_key' => null,
     258      'activation_key_expire' => null,
     259      ),
     260    array('user_id' => $user_id)
     261    );
     262
    225263  $page['infos'][] = l10n('Your password has been reset');
    226 
    227   if (isset($_GET['key']))
    228   {
    229     $page['infos'][] = '<a href="'.get_root_url().'identification.php">'.l10n('Login').'</a>';
    230   }
    231   else
    232   {
    233     $page['infos'][] = '<a href="'.get_gallery_home_url().'">'.l10n('Return to home page').'</a>';
    234   }
     264  $page['infos'][] = '<a href="'.get_root_url().'identification.php">'.l10n('Login').'</a>';
    235265
    236266  return true;
     
    271301}
    272302
    273 if (isset($_GET['key']))
     303if (isset($_GET['key']) and !isset($_POST['submit']))
    274304{
    275305  $user_id = check_password_reset_key($_GET['key']);
Note: See TracChangeset for help on using the changeset viewer.