Version Description
- Log auth cookie failures too.
- Clean up sleep logic. (Bug #1549, deanmarktaylor)
Download this release
Release Info
Developer | convissor |
Plugin | ![]() |
Version | 0.15.0 |
Comparing to | |
See all releases |
Code changes from version 0.14.0 to 0.15.0
- login-security-solution.php +30 -21
- readme.txt +7 -1
- tests/Accessor.php +3 -7
- tests/AuthCookieBadTest.php +105 -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.
|
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
|
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 (
|
1752 |
-
if (
|
1753 |
-
|
1754 |
-
|
1755 |
-
|
1756 |
-
}
|
1757 |
|
1758 |
-
|
1759 |
-
|
1760 |
|
1761 |
-
|
1762 |
-
|
1763 |
-
}
|
1764 |
}
|
1765 |
|
1766 |
return $sleep;
|
@@ -1840,7 +1849,7 @@ class login_security_solution {
|
|
1840 |
wp_logout();
|
1841 |
wp_redirect($uri);
|
1842 |
|
1843 |
-
if (
|
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.
|
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 |
+
}
|