Login Security Solution - Version 0.15.0

Version Description

  • Log auth cookie failures too.
  • Clean up sleep logic. (Bug #1549, deanmarktaylor)
Download this release

Release Info

Developer convissor
Plugin Icon wp plugin Login Security Solution
Version 0.15.0
Comparing to
See all releases

Code changes from version 0.14.0 to 0.15.0

login-security-solution.php CHANGED
@@ -6,7 +6,7 @@
6
  * Description: Requires very strong passwords, repels brute force login attacks, prevents login information disclosures, expires idle sessions, notifies admins of attacks and breaches, permits administrators to disable logins for maintenance or emergency reasons and reset all passwords.
7
  *
8
  * Plugin URI: http://wordpress.org/extend/plugins/login-security-solution/
9
- * Version: 0.14.0
10
  * Author: Daniel Convissor
11
  * Author URI: http://www.analysisandsolutions.com/
12
  * License: GPLv2
@@ -134,12 +134,6 @@ class login_security_solution {
134
  */
135
  protected $table_fail;
136
 
137
- /**
138
- * Is this class being used by our unit tests?
139
- * @var bool
140
- */
141
- protected $testing = false;
142
-
143
  /**
144
  * Our usermeta key for tracking when passwords were changed
145
  * @var string
@@ -181,6 +175,8 @@ class login_security_solution {
181
  public function __construct() {
182
  $this->initialize();
183
 
 
 
184
  add_action('auth_cookie_valid', array(&$this, 'check'), 1, 2);
185
  add_action('password_reset', array(&$this, 'password_reset'), 10, 2);
186
  add_action('user_profile_update_errors',
@@ -297,6 +293,22 @@ class login_security_solution {
297
  * ===== ACTION & FILTER CALLBACK METHODS =====
298
  */
299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  /**
301
  * Removes the current user's last active time metadata
302
  *
@@ -450,7 +462,7 @@ class login_security_solution {
450
  * @return string
451
  *
452
  * @uses login_security_solution::process_login_fail() to log the failure
453
- * and lock the user out if necessary
454
  */
455
  public function login_errors($out = '') {
456
  global $errors, $user_name;
@@ -1738,7 +1750,6 @@ class login_security_solution {
1738
  $this->notify_fail($network_ip, $user_name, $pass_md5, $fails);
1739
  }
1740
 
1741
- $sleep = 0;
1742
  if ($fails['total'] < $this->options['login_fail_tier_2']) {
1743
  // Use random, overlapping sleep times to complicate profiling.
1744
  $sleep = rand(1, 7);
@@ -1748,19 +1759,17 @@ class login_security_solution {
1748
  $sleep = rand(25, 60);
1749
  }
1750
 
1751
- if ($sleep) {
1752
- if (!$this->testing) {
1753
- if (is_multisite()) {
1754
- // Get this cached before disconnecting the database.
1755
- get_option('users_can_register');
1756
- }
1757
 
1758
- // Keep login failures from becoming denial of service attacks.
1759
- mysql_close($wpdb->dbh);
1760
 
1761
- // Increasingly slow down attackers to the point they'll give up.
1762
- sleep($sleep);
1763
- }
1764
  }
1765
 
1766
  return $sleep;
@@ -1840,7 +1849,7 @@ class login_security_solution {
1840
  wp_logout();
1841
  wp_redirect($uri);
1842
 
1843
- if (!$this->testing) {
1844
  exit;
1845
  }
1846
  }
6
  * Description: Requires very strong passwords, repels brute force login attacks, prevents login information disclosures, expires idle sessions, notifies admins of attacks and breaches, permits administrators to disable logins for maintenance or emergency reasons and reset all passwords.
7
  *
8
  * Plugin URI: http://wordpress.org/extend/plugins/login-security-solution/
9
+ * Version: 0.15.0
10
  * Author: Daniel Convissor
11
  * Author URI: http://www.analysisandsolutions.com/
12
  * License: GPLv2
134
  */
135
  protected $table_fail;
136
 
 
 
 
 
 
 
137
  /**
138
  * Our usermeta key for tracking when passwords were changed
139
  * @var string
175
  public function __construct() {
176
  $this->initialize();
177
 
178
+ add_action('auth_cookie_bad_username', array(&$this, 'auth_cookie_bad'));
179
+ add_action('auth_cookie_bad_hash', array(&$this, 'auth_cookie_bad'));
180
  add_action('auth_cookie_valid', array(&$this, 'check'), 1, 2);
181
  add_action('password_reset', array(&$this, 'password_reset'), 10, 2);
182
  add_action('user_profile_update_errors',
293
  * ===== ACTION & FILTER CALLBACK METHODS =====
294
  */
295
 
296
+ /**
297
+ * Passes failed auth cookie data to our login failure process
298
+ *
299
+ * NOTE: This method is automatically called by WordPress when a user's
300
+ * cookie has an invalid user name or password hash.
301
+ *
302
+ * @param array $cookie_elements the auth cookie data
303
+ *
304
+ * @uses login_security_solution::process_login_fail() to log the failure
305
+ * and slow down the response as necessary
306
+ */
307
+ public function auth_cookie_bad($cookie_elements) {
308
+ $this->process_login_fail(@$cookie_elements['username'],
309
+ @$cookie_elements['hmac']);
310
+ }
311
+
312
  /**
313
  * Removes the current user's last active time metadata
314
  *
462
  * @return string
463
  *
464
  * @uses login_security_solution::process_login_fail() to log the failure
465
+ * and slow down the response as necessary
466
  */
467
  public function login_errors($out = '') {
468
  global $errors, $user_name;
1750
  $this->notify_fail($network_ip, $user_name, $pass_md5, $fails);
1751
  }
1752
 
 
1753
  if ($fails['total'] < $this->options['login_fail_tier_2']) {
1754
  // Use random, overlapping sleep times to complicate profiling.
1755
  $sleep = rand(1, 7);
1759
  $sleep = rand(25, 60);
1760
  }
1761
 
1762
+ if (!defined('LOGIN_SECURITY_SOLUTION_TESTING')) {
1763
+ if (is_multisite()) {
1764
+ // Get this cached before disconnecting the database.
1765
+ get_option('users_can_register');
1766
+ }
 
1767
 
1768
+ // Keep login failures from becoming denial of service attacks.
1769
+ mysql_close($wpdb->dbh);
1770
 
1771
+ // Increasingly slow down attackers to the point they'll give up.
1772
+ sleep($sleep);
 
1773
  }
1774
 
1775
  return $sleep;
1849
  wp_logout();
1850
  wp_redirect($uri);
1851
 
1852
+ if (!defined('LOGIN_SECURITY_SOLUTION_TESTING')) {
1853
  exit;
1854
  }
1855
  }
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=danie
4
  Tags: login, password, passwords, strength, strong, idle, timeout, maintenance, security, attack, hack, lock, ban
5
  Requires at least: 3.3
6
  Tested up to: 3.4.1
7
- Stable tag: 0.14.0
8
 
9
  Security against brute force attacks by tracking IP, name, password; requiring very strong passwords. Idle timeout. Maintenance mode. Multisite ready!
10
 
@@ -17,6 +17,7 @@ WordPress installations.
17
  * Blocks brute force and dictionary attacks without inconveniencing
18
  legitimate users or administrators
19
  + Tracks IP addresses, usernames, and passwords
 
20
  + If a login failure uses data matching a past failure, the plugin
21
  slows down response times. The more failures, the longer the delay.
22
  This limits attackers ability to effectively probe your site,
@@ -80,6 +81,7 @@ The tests have caught every password dictionary entry I've tried.
80
  * Uses WordPress' features rather than fighting or overriding them
81
  * No advertising, promotions, or beacons
82
  * Proper internationalization support
 
83
  * Clean, documented code
84
  * Unit tests covering 100% of the main class
85
 
@@ -273,6 +275,10 @@ then `cd` into that directory and run:
273
 
274
  == Changelog ==
275
 
 
 
 
 
276
  = 0.14.0 =
277
  * Fix emails being mistakenly sent in multisite mode that say "There have
278
  been at least 0 failed attempts to log in". (Bug #1548, deanmarktaylor)
4
  Tags: login, password, passwords, strength, strong, idle, timeout, maintenance, security, attack, hack, lock, ban
5
  Requires at least: 3.3
6
  Tested up to: 3.4.1
7
+ Stable tag: 0.15.0
8
 
9
  Security against brute force attacks by tracking IP, name, password; requiring very strong passwords. Idle timeout. Maintenance mode. Multisite ready!
10
 
17
  * Blocks brute force and dictionary attacks without inconveniencing
18
  legitimate users or administrators
19
  + Tracks IP addresses, usernames, and passwords
20
+ + Monitors logins made by form submissions and auth cookies
21
  + If a login failure uses data matching a past failure, the plugin
22
  slows down response times. The more failures, the longer the delay.
23
  This limits attackers ability to effectively probe your site,
81
  * Uses WordPress' features rather than fighting or overriding them
82
  * No advertising, promotions, or beacons
83
  * Proper internationalization support
84
+ * Monitors auth cookie failures
85
  * Clean, documented code
86
  * Unit tests covering 100% of the main class
87
 
275
 
276
  == Changelog ==
277
 
278
+ = 0.15.0 =
279
+ * Log auth cookie failures too.
280
+ * Clean up sleep logic. (Bug #1549, deanmarktaylor)
281
+
282
  = 0.14.0 =
283
  * Fix emails being mistakenly sent in multisite mode that say "There have
284
  been at least 0 failed attempts to log in". (Bug #1548, deanmarktaylor)
tests/Accessor.php CHANGED
@@ -23,6 +23,9 @@ require_once dirname(dirname(__FILE__)) . '/admin.inc';
23
  // Remove automatically created object.
24
  unset($GLOBALS['login_security_solution']);
25
 
 
 
 
26
  /**
27
  * Extend the class to be tested, providing access to protected elements
28
  *
@@ -32,13 +35,6 @@ unset($GLOBALS['login_security_solution']);
32
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
33
  */
34
  class Accessor extends login_security_solution_admin {
35
- /**
36
- * Is this class being used by our unit tests?
37
- * @var bool
38
- */
39
- protected $testing = true;
40
-
41
-
42
  public function __call($method, $args) {
43
  return call_user_func_array(array($this, $method), $args);
44
  }
23
  // Remove automatically created object.
24
  unset($GLOBALS['login_security_solution']);
25
 
26
+ /** Tell the system not to disconnect the database or do the slow downs. */
27
+ define('LOGIN_SECURITY_SOLUTION_TESTING', true);
28
+
29
  /**
30
  * Extend the class to be tested, providing access to protected elements
31
  *
35
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
36
  */
37
  class Accessor extends login_security_solution_admin {
 
 
 
 
 
 
 
38
  public function __call($method, $args) {
39
  return call_user_func_array(array($this, $method), $args);
40
  }
tests/AuthCookieBadTest.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Test the auth cookie failure functionality
5
+ *
6
+ * @package login-security-solution
7
+ * @author Daniel Convissor <danielc@analysisandsolutions.com>
8
+ * @copyright The Analysis and Solutions Company, 2012
9
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
10
+ */
11
+
12
+ /**
13
+ * Get the class we will use for testing
14
+ */
15
+ require_once dirname(__FILE__) . '/TestCase.php';
16
+
17
+ /**
18
+ * Test the auth cookie failure functionality
19
+ *
20
+ * @package login-security-solution
21
+ * @author Daniel Convissor <danielc@analysisandsolutions.com>
22
+ * @copyright The Analysis and Solutions Company, 2012
23
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
24
+ */
25
+ class AuthCookieBadTest extends TestCase {
26
+ protected $ip;
27
+ protected $network_ip;
28
+ protected $user_name;
29
+ protected $pass_md5;
30
+
31
+
32
+ public static function setUpBeforeClass() {
33
+ parent::$db_needed = true;
34
+ parent::set_up_before_class();
35
+ }
36
+
37
+ public function setUp() {
38
+ parent::setUp();
39
+
40
+ if (!$this->is_fail_table_configured()) {
41
+ $this->markTestSkipped("The " . self::$lss->table_fail . " table doesn't exist or isn't using the InnoDB engine. Probably the plugin hasn't been activated.");
42
+ }
43
+
44
+ $this->ip = '1.2.3.4';
45
+ $_SERVER['REMOTE_ADDR'] = $this->ip;
46
+ $this->network_ip = '1.2.3';
47
+
48
+ $this->user_name = 'test';
49
+ $this->pass_md5 = 'ababab';
50
+
51
+ $options = self::$lss->options;
52
+ $options['login_fail_minutes'] = 60;
53
+ $options['login_fail_notify'] = 4;
54
+ $options['login_fail_tier_2'] = 3;
55
+ $options['login_fail_tier_3'] = 4;
56
+ $options['login_fail_breach_notify'] = 4;
57
+ $options['login_fail_breach_pw_force_change'] = 4;
58
+ self::$lss->options = $options;
59
+ }
60
+
61
+
62
+ public function test_direct() {
63
+ $input = array(
64
+ 'username' => $this->user_name,
65
+ 'hmac' => $this->pass_md5,
66
+ );
67
+ self::$lss->auth_cookie_bad($input);
68
+ $pass = self::$lss->md5($this->pass_md5);
69
+ $this->check_fail_record($this->ip, $this->user_name, $pass);
70
+ }
71
+
72
+ /**
73
+ * @depends test_direct
74
+ */
75
+ public function test_bad_user() {
76
+ $_SERVER['REQUEST_METHOD'] = 'GET';
77
+ $_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie(1, time() + 10);
78
+ $parts = explode('|', $_COOKIE[AUTH_COOKIE]);
79
+ $parts[0] = 'thisusercannotpossiblyexist';
80
+ $_COOKIE[AUTH_COOKIE] = implode('|', $parts);
81
+
82
+ $result = wp_validate_auth_cookie();
83
+ $this->assertFalse($result);
84
+
85
+ $pass = self::$lss->md5($parts[2]);
86
+ $this->check_fail_record($this->ip, $parts[0], $pass);
87
+ }
88
+
89
+ /**
90
+ * @depends test_bad_user
91
+ */
92
+ public function test_bad_pass() {
93
+ $_SERVER['REQUEST_METHOD'] = 'GET';
94
+ $_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie(1, time() + 10);
95
+ $parts = explode('|', $_COOKIE[AUTH_COOKIE]);
96
+ $parts[2] = 'badpassword';
97
+ $_COOKIE[AUTH_COOKIE] = implode('|', $parts);
98
+
99
+ $result = wp_validate_auth_cookie();
100
+ $this->assertFalse($result);
101
+
102
+ $pass = self::$lss->md5($parts[2]);
103
+ $this->check_fail_record($this->ip, $parts[0], $pass);
104
+ }
105
+ }