Limit Login Attempts - Version 1.6.0

Version Description

  • Happy New Year
  • Tested against WordPress 3.1-RC1
  • Plugin now requires WordPress version 2.8+. Of course you should never ever use anything but the latest version
  • Fixed deprecation warnings that had been piling up with the old version requirement. Thanks to Johannes Ruthenberg for the report that prompted this
  • Removed auth cookie admin check for version 2.7.
  • Make sure relevant values in $_COOKIE get cleared right away on auth cookie validation failure. There are still some problems with cookie auth handling. The lockout can trigger prematurely in rare cases, but fixing it is plugin version 2 stuff unfortunately.
  • Changed default time for retries to reset from 24 hours to 12 hours. The security impact is very minor and it means the warning will disappear "overnight"
  • Added question to FAQ ("Why not reset failed attempts on a successful login?")
  • Updated screenshots
Download this release

Release Info

Developer johanee
Plugin Icon wp plugin Limit Login Attempts
Version 1.6.0
Comparing to
See all releases

Code changes from version 1.5.2 to 1.6.0

Files changed (4) hide show
  1. limit-login-attempts.php +32 -64
  2. readme.txt +23 -9
  3. screenshot-3.gif +0 -0
  4. screenshot-4.gif +0 -0
limit-login-attempts.php CHANGED
@@ -5,11 +5,11 @@
5
  Description: Limit rate of login attempts, including by way of cookies, for each IP.
6
  Author: Johan Eenfeldt
7
  Author URI: http://devel.kostdoktorn.se
8
- Version: 1.5.2
9
 
10
- Copyright 2008, 2009, 2010 Johan Eenfeldt
11
 
12
- Thanks to Michael Skerwiderski for reverse proxy handling.
13
 
14
  Licenced under the GNU GPL:
15
 
@@ -63,12 +63,9 @@ $limit_login_options =
63
  , 'long_duration' => 86400 // 24 hours
64
 
65
  /* Reset failed attempts after this many seconds */
66
- , 'valid_duration' => 86400 // 24 hours
67
 
68
- /* Also limit malformed/forged cookies?
69
- *
70
- * NOTE: Only works in WP 2.7+, as necessary actions were added then.
71
- */
72
  , 'cookies' => true
73
 
74
  /* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
@@ -96,8 +93,8 @@ limit_login_setup();
96
 
97
  /* Get options and setup filters & actions */
98
  function limit_login_setup() {
99
- load_plugin_textdomain('limit-login-attempts'
100
- , PLUGINDIR.'/'.dirname(plugin_basename(__FILE__)));
101
 
102
  limit_login_setup_options();
103
 
@@ -211,11 +208,19 @@ function limit_login_handle_cookies() {
211
  return;
212
  }
213
 
214
- if (empty($_COOKIE[AUTH_COOKIE]) && empty($_COOKIE[SECURE_AUTH_COOKIE])
215
- && empty($_COOKIE[LOGGED_IN_COOKIE])) {
216
- return;
217
- }
 
 
 
218
 
 
 
 
 
 
219
  wp_clear_auth_cookie();
220
 
221
  if (!empty($_COOKIE[AUTH_COOKIE])) {
@@ -229,20 +234,13 @@ function limit_login_handle_cookies() {
229
  }
230
  }
231
 
232
-
233
- /* Action: failed cookie login wrapper for limit_login_failed() */
234
- function limit_login_failed_cookie($arg) {
235
- limit_login_failed($arg);
236
- wp_clear_auth_cookie();
237
- }
238
-
239
  /*
240
  * Action when login attempt failed
241
  *
242
  * Increase nr of retries (if necessary). Reset valid value. Setup
243
  * lockout if nr of retries are above threshold. And more!
244
  */
245
- function limit_login_failed($arg) {
246
  $ip = limit_login_get_address();
247
 
248
  /* if currently locked-out, do not add to retries */
@@ -302,21 +300,11 @@ function limit_login_failed($arg) {
302
  $lockouts[$ip] = time() + limit_login_option('lockout_duration');
303
  }
304
 
305
- /* try to find username which failed */
306
- $user = '';
307
- if (is_string($arg)) {
308
- /* action: wp_login_failed */
309
- $user = $arg;
310
- } elseif (is_array($arg) && array_key_exists('username', $arg)) {
311
- /* action: auth_cookie_bad_* */
312
- $user = $arg['username'];
313
- }
314
-
315
  /* do housecleaning and save values */
316
  limit_login_cleanup($retries, $lockouts, $valid);
317
 
318
  /* do any notification */
319
- limit_login_notify($user);
320
 
321
  /* increase statistics */
322
  $total = get_option('limit_login_lockouts_total');
@@ -398,13 +386,13 @@ function limit_login_notify_email($user) {
398
  * limit_login_option('allowed_lockouts');
399
  $lockouts = limit_login_option('allowed_lockouts');
400
  $time = round(limit_login_option('long_duration') / 3600);
401
- $when = sprintf(__ngettext('%d hour', '%d hours', $time, 'limit-login-attempts'), $time);
402
  } else {
403
  /* normal lockout */
404
  $count = $retries[$ip];
405
  $lockouts = floor($count / limit_login_option('allowed_retries'));
406
  $time = round(limit_login_option('lockout_duration') / 60);
407
- $when = sprintf(__ngettext('%d minute', '%d minutes', $time, 'limit-login-attempts'), $time);
408
  }
409
 
410
  $blogname = is_limit_login_multisite() ? get_site_option('site_name') : get_option('blogname');
@@ -486,9 +474,9 @@ function limit_login_error_msg() {
486
  $when = ceil(($lockouts[$ip] - time()) / 60);
487
  if ($when > 60) {
488
  $when = ceil($when / 60);
489
- $msg .= sprintf(__ngettext('Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts'), $when);
490
  } else {
491
- $msg .= sprintf(__ngettext('Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts'), $when);
492
  }
493
 
494
  return $msg;
@@ -517,7 +505,7 @@ function limit_login_retries_remaining_msg() {
517
  }
518
 
519
  $remaining = max((limit_login_option('allowed_retries') - ($retries[$ip] % limit_login_option('allowed_retries'))), 0);
520
- return sprintf(__ngettext("<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts'), $remaining);
521
  }
522
 
523
 
@@ -634,13 +622,6 @@ function limit_login_track_credentials($user, $password) {
634
  * Admin stuff
635
  */
636
 
637
- /* Does wordpress version support cookie option? */
638
- function limit_login_support_cookie_option() {
639
- global $wp_version;
640
- return (version_compare($wp_version, '2.7', '>='));
641
- }
642
-
643
-
644
  /* Make a guess if we are behind a proxy or not */
645
  function limit_login_guess_proxy() {
646
  return isset($_SERVER[LIMIT_LOGIN_PROXY_ADDR])
@@ -708,6 +689,8 @@ function limit_login_sanitize_variables() {
708
  limit_login_sanitize_simple_int('allowed_lockouts');
709
  limit_login_sanitize_simple_int('long_duration');
710
 
 
 
711
  $notify_email_after = max(1, intval(limit_login_option('notify_email_after')));
712
  $limit_login_options['notify_email_after'] = min(limit_login_option('allowed_lockouts'), $notify_email_after);
713
 
@@ -721,11 +704,6 @@ function limit_login_sanitize_variables() {
721
  }
722
  $limit_login_options['lockout_notify'] = implode(',', $new_args);
723
 
724
- $cookies = limit_login_option('cookies')
725
- && limit_login_support_cookie_option() ? true : false;
726
-
727
- $limit_login_options['cookies'] = $cookies;
728
-
729
  if ( limit_login_option('client_type') != LIMIT_LOGIN_DIRECT_ADDR
730
  && limit_login_option('client_type') != LIMIT_LOGIN_PROXY_ADDR ) {
731
  $limit_login_options['client_type'] = LIMIT_LOGIN_DIRECT_ADDR;
@@ -760,12 +738,12 @@ function limit_login_show_log($log) {
760
  return;
761
  }
762
 
763
- echo('<tr><th scope="col">' . _c("IP|Internet address", 'limit-login-attempts') . '</th><th scope="col">' . __('Tried to log in as', 'limit-login-attempts') . '</th></tr>');
764
  foreach ($log as $ip => $arr) {
765
  echo('<tr><td class="limit-login-ip">' . $ip . '</td><td class="limit-login-max">');
766
  $first = true;
767
  foreach($arr as $user => $count) {
768
- $count_desc = sprintf(__ngettext('%d lockout', '%d lockouts', $count, 'limit-login-attempts'), $count);
769
  if (!$first) {
770
  echo(', ' . $user . ' (' . $count_desc . ')');
771
  } else {
@@ -847,15 +825,6 @@ function limit_login_option_page() {
847
  $lockouts = get_option('limit_login_lockouts');
848
  $lockouts_now = is_array($lockouts) ? count($lockouts) : 0;
849
 
850
- if (!limit_login_support_cookie_option()) {
851
- $cookies_disabled = ' DISABLED ';
852
- $cookies_note = ' <br /> '
853
- . __('<strong>NOTE:</strong> Only works in Wordpress 2.7 or later'
854
- , 'limit-login-attempts');
855
- } else {
856
- $cookies_disabled = '';
857
- $cookies_note = '';
858
- }
859
  $cookies_yes = limit_login_option('cookies') ? ' checked ' : '';
860
  $cookies_no = limit_login_option('cookies') ? '' : ' checked ';
861
 
@@ -894,7 +863,7 @@ function limit_login_option_page() {
894
  <td>
895
  <?php if ($lockouts_total > 0) { ?>
896
  <input name="reset_total" value="<?php echo __('Reset Counter','limit-login-attempts'); ?>" type="submit" />
897
- <?php echo sprintf(__ngettext('%d lockout since last reset', '%d lockouts since last reset', $lockouts_total, 'limit-login-attempts'), $lockouts_total); ?>
898
  <?php } else { echo __('No lockouts yet','limit-login-attempts'); } ?>
899
  </td>
900
  </tr>
@@ -942,8 +911,7 @@ function limit_login_option_page() {
942
  <tr>
943
  <th scope="row" valign="top"><?php echo __('Handle cookie login','limit-login-attempts'); ?></th>
944
  <td>
945
- <label><input type="radio" name="cookies" <?php echo $cookies_disabled . $cookies_yes; ?> value="1" /> <?php echo __('Yes','limit-login-attempts'); ?></label> <label><input type="radio" name="cookies" <?php echo $cookies_disabled . $cookies_no; ?> value="0" /> <?php echo __('No','limit-login-attempts'); ?></label>
946
- <?php echo $cookies_note ?>
947
  </td>
948
  </tr>
949
  <tr>
5
  Description: Limit rate of login attempts, including by way of cookies, for each IP.
6
  Author: Johan Eenfeldt
7
  Author URI: http://devel.kostdoktorn.se
8
+ Version: 1.6.0
9
 
10
+ Copyright 2008 - 2011 Johan Eenfeldt
11
 
12
+ Thanks to Michael Skerwiderski for reverse proxy handling suggestions.
13
 
14
  Licenced under the GNU GPL:
15
 
63
  , 'long_duration' => 86400 // 24 hours
64
 
65
  /* Reset failed attempts after this many seconds */
66
+ , 'valid_duration' => 43200 // 12 hours
67
 
68
+ /* Also limit malformed/forged cookies? */
 
 
 
69
  , 'cookies' => true
70
 
71
  /* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
93
 
94
  /* Get options and setup filters & actions */
95
  function limit_login_setup() {
96
+ load_plugin_textdomain('limit-login-attempts', false
97
+ , dirname(plugin_basename(__FILE__)));
98
 
99
  limit_login_setup_options();
100
 
208
  return;
209
  }
210
 
211
+ limit_login_clear_auth_cookie();
212
+ }
213
+
214
+
215
+ /* Action: failed cookie login wrapper for limit_login_failed() */
216
+ function limit_login_failed_cookie($cookie_elements) {
217
+ limit_login_clear_auth_cookie();
218
 
219
+ limit_login_failed($cookie_elements['username']);
220
+ }
221
+
222
+ /* Make sure auth cookie really get cleared (for this session too) */
223
+ function limit_login_clear_auth_cookie() {
224
  wp_clear_auth_cookie();
225
 
226
  if (!empty($_COOKIE[AUTH_COOKIE])) {
234
  }
235
  }
236
 
 
 
 
 
 
 
 
237
  /*
238
  * Action when login attempt failed
239
  *
240
  * Increase nr of retries (if necessary). Reset valid value. Setup
241
  * lockout if nr of retries are above threshold. And more!
242
  */
243
+ function limit_login_failed($username) {
244
  $ip = limit_login_get_address();
245
 
246
  /* if currently locked-out, do not add to retries */
300
  $lockouts[$ip] = time() + limit_login_option('lockout_duration');
301
  }
302
 
 
 
 
 
 
 
 
 
 
 
303
  /* do housecleaning and save values */
304
  limit_login_cleanup($retries, $lockouts, $valid);
305
 
306
  /* do any notification */
307
+ limit_login_notify($username);
308
 
309
  /* increase statistics */
310
  $total = get_option('limit_login_lockouts_total');
386
  * limit_login_option('allowed_lockouts');
387
  $lockouts = limit_login_option('allowed_lockouts');
388
  $time = round(limit_login_option('long_duration') / 3600);
389
+ $when = sprintf(_n('%d hour', '%d hours', $time, 'limit-login-attempts'), $time);
390
  } else {
391
  /* normal lockout */
392
  $count = $retries[$ip];
393
  $lockouts = floor($count / limit_login_option('allowed_retries'));
394
  $time = round(limit_login_option('lockout_duration') / 60);
395
+ $when = sprintf(_n('%d minute', '%d minutes', $time, 'limit-login-attempts'), $time);
396
  }
397
 
398
  $blogname = is_limit_login_multisite() ? get_site_option('site_name') : get_option('blogname');
474
  $when = ceil(($lockouts[$ip] - time()) / 60);
475
  if ($when > 60) {
476
  $when = ceil($when / 60);
477
+ $msg .= sprintf(_n('Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts'), $when);
478
  } else {
479
+ $msg .= sprintf(_n('Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts'), $when);
480
  }
481
 
482
  return $msg;
505
  }
506
 
507
  $remaining = max((limit_login_option('allowed_retries') - ($retries[$ip] % limit_login_option('allowed_retries'))), 0);
508
+ return sprintf(_n("<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts'), $remaining);
509
  }
510
 
511
 
622
  * Admin stuff
623
  */
624
 
 
 
 
 
 
 
 
625
  /* Make a guess if we are behind a proxy or not */
626
  function limit_login_guess_proxy() {
627
  return isset($_SERVER[LIMIT_LOGIN_PROXY_ADDR])
689
  limit_login_sanitize_simple_int('allowed_lockouts');
690
  limit_login_sanitize_simple_int('long_duration');
691
 
692
+ $limit_login_options['cookies'] = !!limit_login_option('cookies');
693
+
694
  $notify_email_after = max(1, intval(limit_login_option('notify_email_after')));
695
  $limit_login_options['notify_email_after'] = min(limit_login_option('allowed_lockouts'), $notify_email_after);
696
 
704
  }
705
  $limit_login_options['lockout_notify'] = implode(',', $new_args);
706
 
 
 
 
 
 
707
  if ( limit_login_option('client_type') != LIMIT_LOGIN_DIRECT_ADDR
708
  && limit_login_option('client_type') != LIMIT_LOGIN_PROXY_ADDR ) {
709
  $limit_login_options['client_type'] = LIMIT_LOGIN_DIRECT_ADDR;
738
  return;
739
  }
740
 
741
+ echo('<tr><th scope="col">' . _x("IP", "Internet address", 'limit-login-attempts') . '</th><th scope="col">' . __('Tried to log in as', 'limit-login-attempts') . '</th></tr>');
742
  foreach ($log as $ip => $arr) {
743
  echo('<tr><td class="limit-login-ip">' . $ip . '</td><td class="limit-login-max">');
744
  $first = true;
745
  foreach($arr as $user => $count) {
746
+ $count_desc = sprintf(_n('%d lockout', '%d lockouts', $count, 'limit-login-attempts'), $count);
747
  if (!$first) {
748
  echo(', ' . $user . ' (' . $count_desc . ')');
749
  } else {
825
  $lockouts = get_option('limit_login_lockouts');
826
  $lockouts_now = is_array($lockouts) ? count($lockouts) : 0;
827
 
 
 
 
 
 
 
 
 
 
828
  $cookies_yes = limit_login_option('cookies') ? ' checked ' : '';
829
  $cookies_no = limit_login_option('cookies') ? '' : ' checked ';
830
 
863
  <td>
864
  <?php if ($lockouts_total > 0) { ?>
865
  <input name="reset_total" value="<?php echo __('Reset Counter','limit-login-attempts'); ?>" type="submit" />
866
+ <?php echo sprintf(_n('%d lockout since last reset', '%d lockouts since last reset', $lockouts_total, 'limit-login-attempts'), $lockouts_total); ?>
867
  <?php } else { echo __('No lockouts yet','limit-login-attempts'); } ?>
868
  </td>
869
  </tr>
911
  <tr>
912
  <th scope="row" valign="top"><?php echo __('Handle cookie login','limit-login-attempts'); ?></th>
913
  <td>
914
+ <label><input type="radio" name="cookies" <?php echo $cookies_yes; ?> value="1" /> <?php echo __('Yes','limit-login-attempts'); ?></label> <label><input type="radio" name="cookies" <?php echo $cookies_no; ?> value="0" /> <?php echo __('No','limit-login-attempts'); ?></label>
 
915
  </td>
916
  </tr>
917
  <tr>
readme.txt CHANGED
@@ -1,15 +1,15 @@
1
  === Limit Login Attempts ===
2
  Contributors: johanee
3
  Tags: login, security, authentication
4
- Requires at least: 2.5
5
- Tested up to: 3.0.1
6
- Stable tag: 1.5.2
7
 
8
  Limit rate of login attempts, including by way of cookies, for each IP.
9
 
10
  == Description ==
11
 
12
- Limit the number of login attempts possible both through normal login as well as (WordPress 2.7+) using auth cookies.
13
 
14
  By default WordPress allows unlimited login attempts either through the login page or by sending special cookies. This allows passwords (or hashes) to be brute-force cracked with relative ease.
15
 
@@ -18,7 +18,7 @@ Limit Login Attempts blocks an Internet address from making further attempts aft
18
  Features
19
 
20
  * Limit the number of retry attempts when logging in (for each IP). Fully customizable
21
- * (WordPress 2.7+) Limit the number of attempts to log in using auth cookies in same way
22
  * Informs user about remaining retries or lockout time on login page
23
  * Optional logging, optional email notification
24
  * Handles server behind reverse proxy
@@ -29,14 +29,18 @@ Plugin uses standard actions and filters only.
29
 
30
  == Installation ==
31
 
32
- 1. Download and extract plugin files to a folder in your wp-content/plugin directory.
33
  2. Activate the plugin through the WordPress admin interface.
34
- 3. Customize the settings from the options page, if desired. If your server is located behind a reverse proxy make sure to change this setting.
35
 
36
  If you have any questions or problems please make a post here: http://wordpress.org/tags/limit-login-attempts
37
 
38
  == Frequently Asked Questions ==
39
 
 
 
 
 
40
  = What is this option about site connection and reverse proxy? =
41
 
42
  A reverse proxy is a server in between the site and the Internet (perhaps handling caching or load-balancing). This makes getting the correct client IP to block slightly more complicated.
@@ -59,11 +63,21 @@ If you have access to the database (for example through phpMyAdmin) you can clea
59
 
60
  1. Loginscreen after failed login with retries remaining
61
  2. Loginscreen during lockout
62
- 3. Administration interface in WordPress 2.7
63
- 4. Administration interface in WordPress 2.5
64
 
65
  == Changelog ==
66
 
 
 
 
 
 
 
 
 
 
 
 
67
  = 1.5.2 =
68
  * Reverted minor cookie-handling cleanup which might somehow be responsible for recently reported cookie related lockouts
69
  * Added version 1.x Brazilian Portuguese translation, thanks to Luciano Passuello
1
  === Limit Login Attempts ===
2
  Contributors: johanee
3
  Tags: login, security, authentication
4
+ Requires at least: 2.8
5
+ Tested up to: 3.1-RC1
6
+ Stable tag: 1.6.0
7
 
8
  Limit rate of login attempts, including by way of cookies, for each IP.
9
 
10
  == Description ==
11
 
12
+ Limit the number of login attempts possible both through normal login as well as using auth cookies.
13
 
14
  By default WordPress allows unlimited login attempts either through the login page or by sending special cookies. This allows passwords (or hashes) to be brute-force cracked with relative ease.
15
 
18
  Features
19
 
20
  * Limit the number of retry attempts when logging in (for each IP). Fully customizable
21
+ * Limit the number of attempts to log in using auth cookies in same way
22
  * Informs user about remaining retries or lockout time on login page
23
  * Optional logging, optional email notification
24
  * Handles server behind reverse proxy
29
 
30
  == Installation ==
31
 
32
+ 1. Download and extract plugin files to a wp-content/plugin directory.
33
  2. Activate the plugin through the WordPress admin interface.
34
+ 3. Customize the settings on the options page, if desired. If your server is located behind a reverse proxy make sure to change this setting.
35
 
36
  If you have any questions or problems please make a post here: http://wordpress.org/tags/limit-login-attempts
37
 
38
  == Frequently Asked Questions ==
39
 
40
+ = Why not reset failed attempts on a successful login? =
41
+
42
+ This is very much by design. Otherwise you could brute force the "admin" password by logging in as your own user every 4th attempt.
43
+
44
  = What is this option about site connection and reverse proxy? =
45
 
46
  A reverse proxy is a server in between the site and the Internet (perhaps handling caching or load-balancing). This makes getting the correct client IP to block slightly more complicated.
63
 
64
  1. Loginscreen after failed login with retries remaining
65
  2. Loginscreen during lockout
66
+ 3. Administration interface in WordPress 3.0.4
 
67
 
68
  == Changelog ==
69
 
70
+ = 1.6.0 =
71
+ * Happy New Year
72
+ * Tested against WordPress 3.1-RC1
73
+ * Plugin now requires WordPress version 2.8+. Of course you should never ever use anything but the latest version
74
+ * Fixed deprecation warnings that had been piling up with the old version requirement. Thanks to Johannes Ruthenberg for the report that prompted this
75
+ * Removed auth cookie admin check for version 2.7.
76
+ * Make sure relevant values in $_COOKIE get cleared right away on auth cookie validation failure. There are still some problems with cookie auth handling. The lockout can trigger prematurely in rare cases, but fixing it is plugin version 2 stuff unfortunately.
77
+ * Changed default time for retries to reset from 24 hours to 12 hours. The security impact is very minor and it means the warning will disappear "overnight"
78
+ * Added question to FAQ ("Why not reset failed attempts on a successful login?")
79
+ * Updated screenshots
80
+
81
  = 1.5.2 =
82
  * Reverted minor cookie-handling cleanup which might somehow be responsible for recently reported cookie related lockouts
83
  * Added version 1.x Brazilian Portuguese translation, thanks to Luciano Passuello
screenshot-3.gif CHANGED
Binary file
screenshot-4.gif DELETED
Binary file