Changeset 29111


Ignore:
Timestamp:
07/28/14 21:27:50 (6 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.