There's a new trend in brute force attacks. Most IP addresses are being used a single time. This renders IP address blocking useless.
Traditionally, criminals used a few (compromised) computers to wage brute force attacks against a given site. So security professionals and web masters have monitored IP addresses and set up rules to deny requests from bad actors.
But the increasing proliferation of root kits and sophistication of command and control software has meant attackers can consider each bot (kind of) disposable. They'll use a given computer for one shot, then move on to another computer for the next shot.
Here's an example. One of my sites received a steady stream of 3,496 bogus login requests over a 56 hour period (about 60 per hour) in early January, 2016. The attack came from 2,019 different IP addresses. 1,260 (36%) of the IP's were used only once. Just 3 addresses were used more than 10 times. The most used address only made 15 requests!
Filtering by the first two or three octets of the IP space doesn't get you anything either.
Item | Quantity | Used 1x | Used > 10x | Maximum Used |
---|---|---|---|---|
Unique IPs | 2,019 | 1,260 | 3 | 15 |
Unique First 3 Octets | 1,651 | 871 | 9 | 17 |
Unique First 2 Octets | 744 | 191 | 71 | 80 |
Passwords | 1,191 | n/a | 0 | 4 |
User Names | 3 | n/a | n/a | n/a |
The attacker's control server picks three likely user names ("administrator", "admin", and the blog's name) and one password then tells three bots to try one combination. Then the control server picks another password and has three other bots try those combinations. Rinse and repeat.
I noticed this trend because I'm the author, and user, of the Login Security Solution WordPress plugin. Fortunately, LSS is set up to catch these kinds of attacks by monitoring any combination of IP address (including IPv6), user name, or password. All of the other brute force plugins I've looked at only watch for IP addresses.
Hmm.... Insert your favorite closing quip here by sending it to me on Twitter. :)
I'll be one of the presenters at WordCamp NYC this year. My talk is "Unit Testing WordPress Plugins with PHPUnit," taking place on Saturday, August 2 at 3:30.
Another talk I want to see is "Accessibility Beyond Coding: It’s Everyone’s Responsibility," an issue near and dear to my heart. Alas, it's at the same time as my session.
The whole conference runs from Friday, 8/1 through Sunday, 8/3, so come on down to Brooklyn.
This fourth posting in my WordPress plugin / PHPUnit testing series explains how I verify information inserted into database records with auto-increment IDs in my Object Oriented Plugin Template Solution.
In order for this post to be a be a stand-alone entity, I need to re-explain two terms from the earlier posts. First is "parent class." It holds helper properties and methods for use by multiple test classes.
abstract class TestCase extends PHPUnit_Framework_TestCase {}
The second term is "test class." It extends the "parent class" and contain the test methods that PHPUnit will execute.
class LoginTest extends TestCase {}
Naturally, if we're testing database records, we need a table to put them
in. Below is the table definition from the activate()
method
in the plugin's admin class.
CREATE TABLE `$this->table_login` ( login_id BIGINT(20) NOT NULL AUTO_INCREMENT, user_login VARCHAR(60) NOT NULL DEFAULT '', date_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (login_id), KEY user_login (user_login(5)) )
The real work happens in a method in my "parent class." It uses
WordPress' $wpdb->insert_id in the WHERE
clause of a
query against my table. I then make sure the data matches expectations.
Checking the character data is straight ahead via
assertEquals()
.
Verifying the time stamp is a bit trickier. Since the database server may
be using a different time zone than the computer running the tests, it is
necessary to get the current time from the database, done here via
SYSDATE()
. I then compare the inserted time against the current
time using PHP's DateTime::diff()
.
protected function check_login_record($user_name) { global $wpdb; $this->assertInternalType('integer', $wpdb->insert_id, 'This should be an insert id.'); $sql = 'SELECT *, SYSDATE() AS sysdate FROM `' . self::$o->table_login . '` WHERE login_id = %d'; $actual = $wpdb->get_row($wpdb->prepare($sql, $wpdb->insert_id)); if (!$actual) { $this->fail('Could not find the record in the "login" table.'); } $this->assertEquals($user_name, $actual->user_login, "'user_name' field mismatch."); $date_login = new DateTime($actual->date_login); // Keep tests from going fatal under PHP 5.2. if (method_exists($date_login, 'diff')) { $sysdate = new DateTime($actual->sysdate); $interval = $date_login->diff($sysdate); $this->assertLessThanOrEqual('00000000000001', $interval->format('%Y%M%D%H%I%S'), "'date_login' field off by over 1 second: $actual->date_login."); } }
Now let's utilize that stuff in a test in the "test class."
public function test_insert_login() { self::$o->insert_login($this->user_name); $this->check_login_record($this->user_name); }
That's All Folks. I'm curious what people think of this series. Feel free to submit comments via the form on the post's permalinked page. The full framework, can be found in the Object Oriented Plugin Template Solution.
This third post in my series on testing
WordPress plugins with
PHPUnit
will cover the real world dilemma I ran into combining wp_logout()
,
wp_redirect()
, and continuing a given test's execution after PHP
produces expected errors.
When a PHP error/warning/whatever is encountered inside a test, PHPUnit
throws an exception, immediately stopping execution of that test. It shows the
problem in errors section of the results once all tests have completed. But
what if you want to test the fact
that PHP is supposed to produce an error? PHPUnit lets you do that with
the setExpectedException('PHPUnit_Framework_Error', 'some message')
method or with docblock annotations. It's great stuff.
But there's one big limitation. What I said about PHPUnit "immediately stopping execution of that test" still applies. Any code after the PHP error will not be run (and it's easy to never even realize it). Here's an example.
/** * @expectedException PHPUnit_Framework_Error * @expectedExceptionMessage Notice: Undefined variable: nada */ public function test_error() { echo "$nada\n"; // Execution stops above. The rest of this stuff will not be run. $actual = some_procedure(); $this->assertFalse($actual); }
That test will pass, even if there's a problem with the return from
some_procedure()
. Don't despair! This post explains how to deal
with the problem.
Some of the following concepts are similar to those discussed for
testing wp_mail()
.
Please bear any slight repetition.
First, a bit of background. In this post, when I say "parent class," I mean the class that holds helper properties and methods that all test classes can reference:
abstract class TestCase extends PHPUnit_Framework_TestCase {}
And when I say "test class," I mean the class containing the test methods that PHPUnit will execute:
class LoginTest extends TestCase {}
Monitoring wp_redirect()
itself is pretty easy. WordPress lets
users override the one built into the core. All users have to do is declare
it before WP does. My version calls a static method in the
"parent class."
function wp_redirect($location, $status = 302) { TestCase::wp_redirect($location, $status); }
The redirect method in the "parent class" writes the location to a
property for later comparison. But before doing that, it makes sure that
wp_redirect()
was called at an expected time by checking that the
$location_expected property has been set.
public static function wp_redirect($location, $status) { if (!self::$location_expected) { throw new Exception('wp_redirect() called at unexpected time' . ' ($location_expected was not set).'); } self::$location_actual = $location; }
Here's the plugin's method we'll be testing. It logs the user out and redirects them to the password reset page.
protected function force_retrieve_pw() { wp_logout(); wp_redirect(wp_login_url() . '?action=retrievepassword'); }
The hitch is wp_logout()
calls setcookie()
, which
sends headers, but PHPUnit has already generated some output, meaning (you
guessed it) PHP will produce a warning: Cannot modify header information
- headers already sent by (output started at
PHPUnit/Util/Printer.php:172). That means I won't be able to make sure
the redirect worked because PHPUnit will stop execution when that warning is
thrown.
Thought bubble... I just realized
wp_logout()
andwp_redirect()
are "pluggable" too. So I could override them with methods that don't set cookies and even makes sure the function gets called when I want it to. But that would obviate the need for the cool part of this tutorial. So, uh, let's imagine I never said anything. (Eventually I'll modify the unit tests in the Login Security Solution.)
So, let's set up the error handlers we'll need in the "parent class." The first method is used by the tests to say an error will be coming. It stores the expected error messages in a property and sets an error handler for PHP.
protected function expected_errors($error_messages) { $this->expected_error_list = (array) $error_messages; set_error_handler(array(&$this, 'expected_errors_handler')); }
When PHP produces an error, my error handler makes sure the message is one we anticipated. If so, the $expected_errors_found property is set to true so we can check it later.
public function expected_errors_handler($errno, $errstr) { foreach ($this->expected_error_list as $expect) { if (strpos($errstr, $expect) !== false) { $this->expected_errors_found = true; return true; } } return false; }
Then there is the method that tests can call to make sure the expected PHP error actually happened and to pull my error handler off of the stack.
protected function were_expected_errors_found() { restore_error_handler(); return $this->expected_errors_found; }
Finally! Here's the test for PHPUnit to execute.
public function test_redirect() { // Establish the error handler with the expected message. $expected_error = 'Cannot modify header information'; $this->expected_errors($expected_error); // Let the customized wp_redirect() know it's okay to be called now. self::$location_expected = wp_login_url() . '?action=retrievepassword'; // Call the plugin method we want to test. self::$o->force_retrieve_pw(); // Make sure the error happened. $this->assertTrue($this->were_expected_errors_found(), "Expected error not found: '$expected_error'"); // Check that the redirect URI matches the expected one. $this->assertEquals(self::$location_expected, self::$location_actual, 'wp_redirect() produced unexpected location header.'); }
Well, we made it through. If this was helpful, buy me a beer some time. After figuring this out in the first place and pulling this post together, I can sure use one.
PHPUnit is
a great way to improve and maintain application quality. This post
summarizes how I use PHPUnit to test the behavior of
WordPress' wp_mail()
function in the
Object Oriented Plugin Template Solution.
Before getting into the nitty gritty, let me explain two terms I'll be using. First is "parent class." It holds helper properties and methods for use by multiple test classes.
abstract class TestCase extends PHPUnit_Framework_TestCase {}
The second term is "test class." It extends the "parent class" and contain the test methods that PHPUnit will execute.
class LoginTest extends TestCase {}
Okay, now on with the show...
The wp_mail()
function is "pluggable." Site owners
can override such functions (because WordPress declares their version of a
given function only if it hasn't been implemented yet). So my test framework
takes advantage of this by declaring one first. All my version does is call a
static method in the "parent class."
function wp_mail($to, $subject, $message) { TestCase::mail_to_file($to, $subject, $message); }
My "mail" method in the "parent class" writes the message contents to a temporary file for later comparison. But before doing that, it makes sure that the mail function is actually being called at an expected time by checking that the $mail_file_basename propery has been set.
public static function mail_to_file($to, $subject, $message) { if (!self::$mail_file_basename) { throw new Exception('wp_mail() called at unexpected time' . ' (mail_file_basename was not set).'); } if (!self::$temp_dir) { self::$temp_dir = sys_get_temp_dir(); } // Keep Windows happy by replacing :: in method names with --. $basename = str_replace('::', '--', self::$mail_file_basename); self::$mail_file = self::$temp_dir . '/' . $basename; $contents = 'To: ' . implode(', ', (array) $to) . "\n" . "Subject: $subject\n\n$message"; return file_put_contents(self::$mail_file, $contents, FILE_APPEND); }
Now we need to create the file containing the expected message. The files
are generally named for the test method where the mail is being triggered from,
using the format produced by the __METHOD__
magic constant but with
::
replaced with --
to keep Windows from freaking
out. For this example it's LoginTest--test_notify_login
.
Since the text of the actual messages can be composed with translated
strings, we need to provide separate expected message files for each supported
translation. The files for a given translation go into a subdirectory of
tests/expected
named for the
WPLANG
to be tested. The default directory is tests/expected/en_US
.
Here's the expected file for this example, using format placeholders for strings that can vary in different environments:
To: %a Subject: howdy %s just logged in to %s.
The actual test in the "test class" is pretty straight forward. The comments explain what's going on.
public function test_notify_login() { // Set the name of the file holding the expected output. self::$mail_file_basename = __METHOD__; // Call the method that generates the mail. (See declaration, below.) self::$o->notify_login($this->user_name); // Compare the generated message to the expected one. (See below.) $this->check_mail_file(); }
The plugin's method that generates the email:
protected function notify_login($user_name) { $to = get_site_option('admin_email'); $subject = 'howdy'; $blog = get_option('blogname'); // __() passes the string though the translation process. $message = sprintf(__("%s just logged in to %s.", self::ID), $user_name, $blog) . "\n"; return wp_mail($to, $subject, $message); }
Back in the "parent class," there is a method for comparing the
files containing the expected and actual messages. The first thing it does
is check that wp_mail()
was really executed.
protected function check_mail_file() { // Ensure the actual output was generated. if (!self::$mail_file) { $this->fail('wp_mail() has not been called.'); } $basedir = dirname(__FILE__) . '/expected/'; $locale = get_locale(); if (!file_exists("$basedir/$locale")) { $locale = 'en_US'; } // Placate Microsoft. $basename = str_replace('::', '--', self::$mail_file_basename); // Use PHPUnit's file diff method. $this->assertStringMatchesFormatFile( "$basedir/$locale/$basename", file_get_contents(self::$mail_file) ); }
If everything worked as planned, assertStringMatchesFormatFile()
will tell PHPUnit that the test passed. If not, it'll produce a diff between
the expected and actual output. Do note, there's
a bug in
PHPUnit 3.6 that shows all lines as having a diff, not just the
ones that actually have a diff. This is fixed in PHPUnit 3.7.
Well, that's it. Let me know if this was helpful. To examine the full framework, download the Object Oriented Plugin Template Solution from WordPress' plugin directory.
Creating the Login Security Solution taught me a lot about designing clean, solid WordPress plugins. It seemed like a good idea to distill those concepts into a skeleton that other developers can use for creating or augmenting their own plugins. So please raise a toast for the Object Oriented Plugin Template Solution. Now I'll throw some more logs under the boiler to extract a few essences into some quick tutorials.
The initial posts will cover unit testing WordPress plugins with PHPUnit. This first article shows how to work around the conflict between WordPress' heavy reliance on globally scoped variables (groan) and PHPUnit's squashing of global variables. This mayhem results in several errors:
Place this at the top of the script that sets up the environment:
global $current_blog, $current_site, $wp_rewrite, $wpdb; $_SERVER['HTTP_HOST'] = 'localhost';
Add this property to your parent test case class that extends
PHPUnit_Framework_TestCase
:
protected $backupGlobals = false;
Now require_once
the wp-load.php
file and you're good to go.
To examine the full framework, download the Object Oriented Plugin Template Solution from WordPress' plugin directory.