SECURITY: Various security enhancements to password resets. They are now rate-limited by username and IP, and it is possible to disable username autofill for guests.
authorDan Fuhry <dan@enanocms.org>
Sun, 04 Sep 2011 02:32:49 -0400
changeset 1352 d97cf005f674
parent 1351 a57727e67241
child 1353 1e8c224d47ef
SECURITY: Various security enhancements to password resets. They are now rate-limited by username and IP, and it is possible to disable username autofill for guests.
includes/sessions.php
includes/template.php
language/english/admin.json
plugins/SpecialAdmin.php
plugins/SpecialPageFuncs.php
plugins/admin/SecurityLog.php
--- a/includes/sessions.php	Fri Jul 22 23:14:06 2011 -0400
+++ b/includes/sessions.php	Sun Sep 04 02:32:49 2011 -0400
@@ -2195,6 +2195,24 @@
 		$row = $db->fetchrow();
 		$temp_pass = $this->random_pass();
 		
+		// check the seclog
+		$ts = time() - 15;
+		// Prevent resets on the same UID or from the same IP
+		// FIXME: Qatar
+		// 		(http://www.reddit.com/r/todayilearned/comments/k27x6/til_that_the_entire_nation_of_qatar_has_a_single/)
+		$q = $this->sql('SELECT log_id, time_id FROM ' . table_prefix . "logs WHERE log_type = 'security' AND action = 'pass_reset' AND ( edit_summary = '{$_SERVER['REMOTE_ADDR']}' OR author_uid = {$row['user_id']} ) AND time_id > $ts;");
+		if ( $db->numrows($q) > 0 )
+		{
+			// rate limit exceeded... one password reset request every 15 seconds is not appropriate
+			$db->free_result();
+			return false;
+		}
+		$db->free_result();
+		
+		$now = time();
+		$uname = $db->escape($row['username']);
+		$this->sql('INSERT INTO ' . table_prefix . 'logs ( time_id, log_type, action, author, author_uid, edit_summary ) VALUES'
+						. "($now, 'security', 'pass_reset', '$uname', {$row['user_id']}, '{$_SERVER['REMOTE_ADDR']}');");
 		$this->register_temp_password($row['user_id'], $temp_pass);
 		
 		$site_name = getConfig('site_name');
--- a/includes/template.php	Fri Jul 22 23:14:06 2011 -0400
+++ b/includes/template.php	Sun Sep 04 02:32:49 2011 -0400
@@ -2118,6 +2118,15 @@
  	
 	function username_field($name, $value = false)
 	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		$value = $value ? htmlspecialchars($value) : false;
+		
+		$val = $value ? "value=\"" . $value . "\"" : "";
+		
+		if ( $session->user_id == 1 && getConfig('autofill_username_for_guests', 0) != 1 )
+			return "<input name=\"$name\" type=\"text\" size=\"30\" $val />";
+		
 		$randomid = md5( time() . microtime() . mt_rand() );
 		$text = '<input name="'.$name.'" class="autofill username" onkeyup="new AutofillUsername(this);" type="text" size="30" id="userfield_'.$randomid.'" autocomplete="off"';
 		if($value) $text .= ' value="'.$value.'"';
--- a/language/english/admin.json	Fri Jul 22 23:14:06 2011 -0400
+++ b/language/english/admin.json	Sun Sep 04 02:32:49 2011 -0400
@@ -465,6 +465,9 @@
 			field_userpage_acl_title: 'New users can edit their user pages:',
 			field_userpage_acl_hint: 'This setting will cause Enano to grant certain rights to newly registered users, effective only on their user page. These rights include creating their page, editing their page, and posting comments. Since it generates a new ACL rule, you are able to override new permissions. This setting will only take effect on new users - it does not affect the permissions of those already registered.',
 			field_userpage_acl: 'Grant editing rights to new users on their user pages',
+			field_guest_autofill_title: 'Enable username auto-completion for guests:',
+			field_guest_autofill_hint: 'Fields where a username is expected will auto-complete for everyone, including guest users, if this option is enabled. This is a convenience option but potentially also a security risk. If this option is unchecked, username completion will only be enabled for logged-in users.',
+			field_guest_autofill: 'Auto-complete usernames for all users, including guests',
 			
 			// Main section: sidebar links
 			heading_sidebar: 'Sidebar links',
@@ -1130,6 +1133,7 @@
 			entry_u_to_admin: 'User %username% added to Administrators group',
 			entry_u_to_mod: 'User %username% added to Moderators group',
 			entry_view_comment_ip: 'IP address viewed on comment by %username%',
+			entry_pass_reset: 'Password reset requested',
 			tip_reverse_dns: 'Click for reverse DNS info',
 			
 			err_invalid_ip: 'No valid IPv4 or IPv6 address was provided. (In the demo, this is to be expected.)',
--- a/plugins/SpecialAdmin.php	Fri Jul 22 23:14:06 2011 -0400
+++ b/plugins/SpecialAdmin.php	Sun Sep 04 02:32:49 2011 -0400
@@ -270,6 +270,7 @@
 		setConfig('avatar_directory', 'files/avatars');
 		
 		setConfig('userpage_grant_acl', ( isset($_POST['userpage_grant_acl']) ? '1' : '0' ));
+		setConfig('autofill_username_for_guests', ( isset($_POST['autofill_username_for_guests']) ? '1' : '0' ));
 		setConfig('gzip_output', ( isset($_POST['gzip_output']) ? '1' : '0' ));
 		
 		if ( isset($_POST['trust_xff']) )
@@ -977,6 +978,21 @@
 				</td>
 			</tr>
 			
+			<tr>
+				<td class="row1">
+					<b><?php echo $lang->get('acpgc_field_guest_autofill_title'); ?></b><br />
+					<small>
+						<?php echo $lang->get('acpgc_field_guest_autofill_hint'); ?>
+					</small>
+				</td>
+				<td class="row1">
+					<label>
+						<input type="checkbox" name="autofill_username_for_guests" <?php if ( getConfig('autofill_username_for_guests', 0) == '1' ) echo 'checked="checked" '; ?>/>
+						<?php echo $lang->get('acpgc_field_guest_autofill'); ?>
+					</label>
+				</td>
+			</tr>
+			
 		<!-- Allow plugins to add code -->
 			<?php
 			$code = $plugins->setHook('acp_general_users');
--- a/plugins/SpecialPageFuncs.php	Fri Jul 22 23:14:06 2011 -0400
+++ b/plugins/SpecialPageFuncs.php	Sun Sep 04 02:32:49 2011 -0400
@@ -643,6 +643,9 @@
 			case 'username':
 				if ( isset($_GET['userinput']) && strlen($_GET['userinput']) >= 3 )
 				{
+					if ( $session->user_id == 1 && getConfig('autofill_username_for_guests', 0) != 1 )
+						break;
+					
 					$search = '%' . escape_string_like($_GET['userinput']) . '%';
 					$lsearch = strtolower($search);
 					$min_id = ( isset($_GET['allow_anon']) && $_GET['allow_anon'] == '1' ) ? '1' : '2';
--- a/plugins/admin/SecurityLog.php	Fri Jul 22 23:14:06 2011 -0400
+++ b/plugins/admin/SecurityLog.php	Sun Sep 04 02:32:49 2011 -0400
@@ -180,6 +180,7 @@
 		case "u_to_admin"      : $return .= $lang->get('acpsl_entry_u_to_admin'       , array('username' => $r['page_text'])); break;
 		case "u_to_mod"        : $return .= $lang->get('acpsl_entry_u_to_mod'         , array('username' => $r['page_text'])); break;
 		case "view_comment_ip" : $return .= $lang->get('acpsl_entry_view_comment_ip'  , array('username' => htmlspecialchars($r['page_text']))); break;
+		case "pass_reset"      : $return .= $lang->get('acpsl_entry_pass_reset'); break;
 	}
 	$author_bit = '<span style="';
 	$rank_info = $session->get_user_rank($r['author_uid']);