Limit Login Attempts Reloaded - Version 2.2.0

Version Description

  • Removed the "Handle cookie login" setting as they are now obsolete.
  • Added bruteforce protection against Woocommerce login page attacks. https://wordpress.org/support/topic/how-to-integrate-with-woocommerce-2/
  • Added bruteforce protection against XMLRPC attacks. https://wordpress.org/support/topic/xmlrpc-7/
Download this release

Release Info

Developer wpchefgadget
Plugin Icon 128x128 Limit Login Attempts Reloaded
Version 2.2.0
Comparing to
See all releases

Code changes from version 2.1.0 to 2.2.0

core/LimitLoginAttempts.php CHANGED
@@ -60,29 +60,106 @@ class Limit_Login_Attempts {
60
  */
61
  public function hooks_init() {
62
  add_action( 'plugins_loaded', array($this, 'setup'), 9999 );
63
-
64
  add_action( 'admin_enqueue_scripts', array($this, 'enqueue') );
 
65
 
66
- add_action( 'auth_cookie_bad_username', array($this, 'failed_cookie') );
67
- add_action( 'wp_login_failed', array($this, 'limit_login_failed') );
68
- if( $this->get_option( 'cookies' ) ) {
69
- $this->handle_cookies();
 
 
70
 
71
- global $wp_version;
 
 
72
 
73
- if( version_compare( $wp_version, '3.0', '>=' ) ) {
74
- add_action( 'auth_cookie_bad_hash', array($this, 'failed_cookie_hash') );
75
- add_action( 'auth_cookie_valid', array($this, 'valid_cookie'), 10, 2 );
76
- } else {
77
- add_action( 'auth_cookie_bad_hash', array($this, 'failed_cookie') );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  add_filter( 'wp_authenticate_user', array($this, 'wp_authenticate_user'), 99999, 2 );
81
  add_filter( 'shake_error_codes', array($this, 'failure_shake') );
82
  add_action( 'login_head', array($this, 'add_error_message') );
83
  add_action( 'login_errors', array($this, 'fixup_error_messages') );
84
  add_action( 'admin_menu', array($this, 'admin_menu') );
85
 
 
 
 
 
 
 
86
  /*
87
  * This action should really be changed to the 'authenticate' filter as
88
  * it will probably be deprecated. That is however only available in
@@ -91,13 +168,6 @@ class Limit_Login_Attempts {
91
  add_action( 'wp_authenticate', array($this, 'track_credentials'), 10, 2 );
92
  }
93
 
94
- public function setup() {
95
- $this->check_original_installed();
96
-
97
- load_plugin_textdomain( 'limit-login-attempts-reloaded', false, plugin_basename( dirname( __FILE__ ) ) . '/../languages' );
98
- $this->setup_options();
99
- }
100
-
101
  /**
102
  * Check if the original plugin is installed
103
  */
@@ -402,6 +472,7 @@ class Limit_Login_Attempts {
402
  * @param $username
403
  */
404
  public function limit_login_failed( $username ) {
 
405
  $ip = $this->get_address();
406
 
407
  /* if currently locked-out, do not add to retries */
@@ -674,6 +745,7 @@ class Limit_Login_Attempts {
674
  * @return \WP_Error
675
  */
676
  public function wp_authenticate_user( $user, $password ) {
 
677
  if( is_wp_error( $user ) || $this->is_limit_login_ok() ) {
678
  return $user;
679
  }
@@ -821,7 +893,8 @@ class Limit_Login_Attempts {
821
  if( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
822
  /* Replace error message, including ours if necessary */
823
  $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
824
- if( $limit_login_my_error_shown ) {
 
825
  $content .= "<br />\n" . $this->get_message() . "<br />\n";
826
  }
827
 
@@ -841,6 +914,10 @@ class Limit_Login_Attempts {
841
  return $new;
842
  }
843
 
 
 
 
 
844
  /**
845
  * Return current (error) message to show, if any
846
  *
60
  */
61
  public function hooks_init() {
62
  add_action( 'plugins_loaded', array($this, 'setup'), 9999 );
 
63
  add_action( 'admin_enqueue_scripts', array($this, 'enqueue') );
64
+ }
65
 
66
+ /**
67
+ * @param $error IXR_Error
68
+ *
69
+ * @return IXR_Error
70
+ */
71
+ public function xmlrpc_error_messages( $error ) {
72
 
73
+ if( !class_exists( 'IXR_Error' ) ) {
74
+ return $error;
75
+ }
76
 
77
+ if( !$this->is_limit_login_ok() ) {
78
+ return new IXR_Error( 403, $this->error_msg() );
79
+ }
80
+
81
+ $ip = $this->get_address();
82
+ $retries = get_option( 'limit_login_retries' );
83
+ $valid = get_option( 'limit_login_retries_valid' );
84
+
85
+ /* Should we show retries remaining? */
86
+
87
+ if( !is_array( $retries ) || !is_array( $valid ) ) {
88
+ /* no retries at all */
89
+ return $error;
90
+ }
91
+ if( !isset( $retries[ $ip ] ) || !isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
92
+ /* no: no valid retries */
93
+ return $error;
94
+ }
95
+ if( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
96
+ /* no: already been locked out for these retries */
97
+ return $error;
98
+ }
99
+
100
+ $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
101
+ return new IXR_Error( 403, sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining ) );
102
+ }
103
+
104
+ /**
105
+ * Errors on WooCommerce account page
106
+ */
107
+ public function add_wc_notices() {
108
+
109
+ global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
110
+
111
+ if( !function_exists( 'is_account_page' ) || !function_exists( 'wc_add_notice' ) ) {
112
+ return;
113
+ }
114
+
115
+ /*
116
+ * During lockout we do not want to show any other error messages (like
117
+ * unknown user or empty password).
118
+ */
119
+ if( empty( $_POST ) && !$this->is_limit_login_ok() && !$limit_login_just_lockedout ) {
120
+ if( is_account_page() ) {
121
+ wc_add_notice( $this->error_msg(), 'error' );
122
  }
123
  }
124
+
125
+ }
126
+
127
+ public function setup() {
128
+ $this->check_original_installed();
129
+
130
+ load_plugin_textdomain( 'limit-login-attempts-reloaded', false, plugin_basename( dirname( __FILE__ ) ) . '/../languages' );
131
+ $this->setup_options();
132
+
133
+ add_action( 'wp_login_failed', array($this, 'limit_login_failed') );
134
+
135
+ // TODO: remove this
136
+ // if( $this->get_option( 'cookies' ) ) {
137
+ // $this->handle_cookies();
138
+ //
139
+ // add_action( 'auth_cookie_bad_username', array($this, 'failed_cookie') );
140
+ //
141
+ // global $wp_version;
142
+ //
143
+ // if( version_compare( $wp_version, '3.0', '>=' ) ) {
144
+ // add_action( 'auth_cookie_bad_hash', array($this, 'failed_cookie_hash') );
145
+ // add_action( 'auth_cookie_valid', array($this, 'valid_cookie'), 10, 2 );
146
+ // } else {
147
+ // add_action( 'auth_cookie_bad_hash', array($this, 'failed_cookie') );
148
+ // }
149
+ // }
150
+
151
  add_filter( 'wp_authenticate_user', array($this, 'wp_authenticate_user'), 99999, 2 );
152
  add_filter( 'shake_error_codes', array($this, 'failure_shake') );
153
  add_action( 'login_head', array($this, 'add_error_message') );
154
  add_action( 'login_errors', array($this, 'fixup_error_messages') );
155
  add_action( 'admin_menu', array($this, 'admin_menu') );
156
 
157
+ // Add notices for XMLRPC request
158
+ add_filter( 'xmlrpc_login_error', array($this, 'xmlrpc_error_messages') );
159
+
160
+ // Add notices to woocommerce login page
161
+ add_action( 'wp_head', array($this, 'add_wc_notices') );
162
+
163
  /*
164
  * This action should really be changed to the 'authenticate' filter as
165
  * it will probably be deprecated. That is however only available in
168
  add_action( 'wp_authenticate', array($this, 'track_credentials'), 10, 2 );
169
  }
170
 
 
 
 
 
 
 
 
171
  /**
172
  * Check if the original plugin is installed
173
  */
472
  * @param $username
473
  */
474
  public function limit_login_failed( $username ) {
475
+
476
  $ip = $this->get_address();
477
 
478
  /* if currently locked-out, do not add to retries */
745
  * @return \WP_Error
746
  */
747
  public function wp_authenticate_user( $user, $password ) {
748
+
749
  if( is_wp_error( $user ) || $this->is_limit_login_ok() ) {
750
  return $user;
751
  }
893
  if( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
894
  /* Replace error message, including ours if necessary */
895
  $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
896
+
897
+ if( $limit_login_my_error_shown || $this->get_message() ) {
898
  $content .= "<br />\n" . $this->get_message() . "<br />\n";
899
  }
900
 
914
  return $new;
915
  }
916
 
917
+ public function fixup_error_messages_wc( \WP_Error $error ) {
918
+ $error->add( 1, __( 'WC Error' ) );
919
+ }
920
+
921
  /**
922
  * Return current (error) message to show, if any
923
  *
limit-login-attempts-reloaded.php CHANGED
@@ -4,7 +4,7 @@
4
  Description: Limit the rate of login attempts, including by way of cookies and for each IP address.
5
  Author: wpchefgadget
6
  Text Domain: limit-login-attempts-reloaded
7
- Version: 2.1.0
8
 
9
  Copyright 2008 - 2012 Johan Eenfeldt
10
 
4
  Description: Limit the rate of login attempts, including by way of cookies and for each IP address.
5
  Author: wpchefgadget
6
  Text Domain: limit-login-attempts-reloaded
7
+ Version: 2.2.0
8
 
9
  Copyright 2008 - 2012 Johan Eenfeldt
10
 
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: wpchefgadget
3
  Tags: login, security, authentication, Limit Login Attempts, Limit Login Attempts Reloaded, Limit Login Attempts Revamped, Limit Login Attempts Renovated, Limit Login Attempts Updated, Better Limit Login Attempts, Limit Login Attempts Renewed, Limit Login Attempts Upgraded
4
  Requires at least: 2.8
5
- Tested up to: 4.6
6
- Stable tag: 2.1.0
7
 
8
  Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers.
9
 
@@ -21,6 +21,8 @@ Features:
21
  * Optional logging and optional email notification.
22
  * Handles server behind the reverse proxy.
23
  * It is possible to whitelist IPs using a filter. But you probably shouldn't do this.
 
 
24
 
25
  = Upgrading from the old Limit Login Attempts plugin =
26
  1. Go to the Plugins section in your site's backend.
@@ -46,6 +48,11 @@ Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
46
 
47
  == Changelog ==
48
 
 
 
 
 
 
49
  = 2.1.0 =
50
  * The site connection settings are now applied automatically and therefore have been removed from the admin interface.
51
  * Now compatible with PHP 5.2 to support some older WP installations.
2
  Contributors: wpchefgadget
3
  Tags: login, security, authentication, Limit Login Attempts, Limit Login Attempts Reloaded, Limit Login Attempts Revamped, Limit Login Attempts Renovated, Limit Login Attempts Updated, Better Limit Login Attempts, Limit Login Attempts Renewed, Limit Login Attempts Upgraded
4
  Requires at least: 2.8
5
+ Tested up to: 4.6.1
6
+ Stable tag: 2.2.0
7
 
8
  Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers.
9
 
21
  * Optional logging and optional email notification.
22
  * Handles server behind the reverse proxy.
23
  * It is possible to whitelist IPs using a filter. But you probably shouldn't do this.
24
+ * XMLRPC gateway protection.
25
+ * Woocommerce login page protection.
26
 
27
  = Upgrading from the old Limit Login Attempts plugin =
28
  1. Go to the Plugins section in your site's backend.
48
 
49
  == Changelog ==
50
 
51
+ = 2.2.0 =
52
+ * Removed the "Handle cookie login" setting as they are now obsolete.
53
+ * Added bruteforce protection against Woocommerce login page attacks. https://wordpress.org/support/topic/how-to-integrate-with-woocommerce-2/
54
+ * Added bruteforce protection against XMLRPC attacks. https://wordpress.org/support/topic/xmlrpc-7/
55
+
56
  = 2.1.0 =
57
  * The site connection settings are now applied automatically and therefore have been removed from the admin interface.
58
  * Now compatible with PHP 5.2 to support some older WP installations.
views/options-page.php CHANGED
@@ -44,7 +44,6 @@ if( isset( $_POST[ 'update_options' ] ) ) {
44
  $this->_options[ 'allowed_lockouts' ] = $_POST[ 'allowed_lockouts' ];
45
  $this->_options[ 'long_duration' ] = $_POST[ 'long_duration' ] * 3600;
46
  $this->_options[ 'notify_email_after' ] = $_POST[ 'email_after' ];
47
- $this->_options[ 'cookies' ] = ( isset( $_POST[ 'cookies' ] ) && $_POST[ 'cookies' ] == '1' );
48
 
49
  $notify_methods = array();
50
  if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
@@ -65,9 +64,6 @@ $lockouts_total = get_option( 'limit_login_lockouts_total', 0 );
65
  $lockouts = get_option( 'limit_login_lockouts' );
66
  $lockouts_now = is_array( $lockouts ) ? count( $lockouts ) : 0;
67
 
68
- $cookies_yes = $this->get_option( 'cookies' ) ? ' checked ' : '';
69
- $cookies_no = $this->get_option( 'cookies' ) ? '' : ' checked ';
70
-
71
  $v = explode( ',', $this->get_option( 'lockout_notify' ) );
72
  $log_checked = in_array( 'log', $v ) ? ' checked ' : '';
73
  $email_checked = in_array( 'email', $v ) ? ' checked ' : '';
@@ -96,7 +92,7 @@ $email_checked = in_array( 'email', $v ) ? ' checked ' : '';
96
  <th scope="row"
97
  valign="top"><?php echo __( 'Active lockouts', 'limit-login-attempts-reloaded' ); ?></th>
98
  <td>
99
- <input name="reset_current"
100
  value="<?php echo __( 'Restore Lockouts', 'limit-login-attempts-reloaded' ); ?>"
101
  type="submit"/>
102
  <?php echo sprintf( __( '%d IP is currently blocked from trying to log in', 'limit-login-attempts-reloaded' ), $lockouts_now ); ?>
@@ -131,16 +127,6 @@ $email_checked = in_array( 'email', $v ) ? ' checked ' : '';
131
  name="valid_duration"/> <?php echo __( 'hours until retries are reset', 'limit-login-attempts-reloaded' ); ?>
132
  </td>
133
  </tr>
134
- <tr>
135
- <th scope="row"
136
- valign="top"><?php echo __( 'Handle cookie login', 'limit-login-attempts-reloaded' ); ?></th>
137
- <td>
138
- <label><input type="radio" name="cookies" <?php echo $cookies_yes; ?>
139
- value="1"/> <?php echo __( 'Yes', 'limit-login-attempts-reloaded' ); ?></label>
140
- <label><input type="radio" name="cookies" <?php echo $cookies_no; ?>
141
- value="0"/> <?php echo __( 'No', 'limit-login-attempts-reloaded' ); ?></label>
142
- </td>
143
- </tr>
144
  <tr>
145
  <th scope="row"
146
  valign="top"><?php echo __( 'Notify on lockout', 'limit-login-attempts-reloaded' ); ?></th>
44
  $this->_options[ 'allowed_lockouts' ] = $_POST[ 'allowed_lockouts' ];
45
  $this->_options[ 'long_duration' ] = $_POST[ 'long_duration' ] * 3600;
46
  $this->_options[ 'notify_email_after' ] = $_POST[ 'email_after' ];
 
47
 
48
  $notify_methods = array();
49
  if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
64
  $lockouts = get_option( 'limit_login_lockouts' );
65
  $lockouts_now = is_array( $lockouts ) ? count( $lockouts ) : 0;
66
 
 
 
 
67
  $v = explode( ',', $this->get_option( 'lockout_notify' ) );
68
  $log_checked = in_array( 'log', $v ) ? ' checked ' : '';
69
  $email_checked = in_array( 'email', $v ) ? ' checked ' : '';
92
  <th scope="row"
93
  valign="top"><?php echo __( 'Active lockouts', 'limit-login-attempts-reloaded' ); ?></th>
94
  <td>
95
+ <input class="button" name="reset_current"
96
  value="<?php echo __( 'Restore Lockouts', 'limit-login-attempts-reloaded' ); ?>"
97
  type="submit"/>
98
  <?php echo sprintf( __( '%d IP is currently blocked from trying to log in', 'limit-login-attempts-reloaded' ), $lockouts_now ); ?>
127
  name="valid_duration"/> <?php echo __( 'hours until retries are reset', 'limit-login-attempts-reloaded' ); ?>
128
  </td>
129
  </tr>
 
 
 
 
 
 
 
 
 
 
130
  <tr>
131
  <th scope="row"
132
  valign="top"><?php echo __( 'Notify on lockout', 'limit-login-attempts-reloaded' ); ?></th>