[DEVELOPING] Possible Email DDOS attack exploiting MailPoet (WordPress plugin)

This is a developing issue happening right now, please check back on this page for any updates over the following days.

Last updated 2018-03-29

Starting around March 25, 2018, I started seeing an unusual amount of bounces from my email server. The emails bouncing were the double-opt-in “please confirm your subscription” messages automatically generated by the WordPress plugin MailPoet.  This happens from time to time if, for example, somebody accidentally mis-types their email address. But now, suddenly they were coming in repeatedly for the same handful of addresses, including ones like support@linode.com and abuse@linode.com. So out of curiosity I headed over the Linode Status page, and saw this:

Investigating – We are currently experiencing issues with receiving emails on support@linode.com and abuse@linode.com. Our team is investigating this issue and we are working as quickly as possible to have email restored.

Mar 26, 03:08 UTC

This was now beginning to look like some new, novel way of launching an email denial-of-service attack… flooding a particular address with endless MailPoet opt-in emails.  Without gaining any login access, the attackers are somehow able to use my site (and who knows how many others) as part of their attack.  MailPoet is a very popular WordPress plugin — there are plenty of sites out there using it.

How the attack occurs

When you install MailPoet for the first time, it creates a default list.  In my case it’s called “admin”, with a list ID of 1.  I don’t actually use this list for anything, my actual subscription sign up form goes to a different list I created.  Yet somehow new email sign ups started appearing in this list that I don’t use.

I decided to start logging POST data for a while to find out how the attackers were doing this.  So I added the following code to my site’s wp-config.php file:

if ( isset($_POST) && is_array($_POST) && count($_POST) > 0 ) {
	$log_name = dirname( __FILE__ ) . '/requests.log';
	$log_entry = gmdate('r') . "\t" . $_SERVER['REMOTE_ADDR'] . "\t" . $_SERVER['REQUEST_URI'] . "
	" . print_r($_POST,true) . "\n\n";
	$fp=fopen( $log_name, 'a' ); 
	fputs($fp, $log_entry); 
	fclose($fp);
}

It didn’t take long to find out exactly how they are doing it. Here it is:

Wed, 28 Mar 2018 06:58:49 +0000 185.65.135.173  /wp-admin/admin-ajax.php
        Array
(
    [action] => wysija_ajax
    [ajaxurl] => http://www.mailpoet.com/wp-admin/admin-ajax.php
    [controller] => subscribers
    [data] => Array
        (
            [0] => Array
                (
                    [name] => wysija[user][firstname]
                    [value] => uZgfjIJufYovmuKt
                )

            [1] => Array
                (
                    [name] => wysija[user][abs][firstname]
                    [value] =>
                )

            [2] => Array
                (
                    [name] => wysija[user][email]
                    [value] => caker@linode.com
                )

            [3] => Array
                (
                    [name] => wysija[user][abs][email]
                    [value] =>
                )

            [4] => Array
                (
                    [name] => action
                    [value] => save
                )

            [5] => Array
                (
                    [name] => controller
                    [value] => subscribers
                )

            [6] => Array
                (
                    [name] => wysija[user_list][list_ids]
                    [value] => 1
                )

        )

    [task] => save
)

So they are POSTing to my /wp-admin/admin-ajax.php file, a call to action ‘wysija_ajax’, with the relevant data to add a subscriber to MailPoet. As you can see at the bottom, they are assigning the subscriber to list ID 1, or in other words the default list that everybody probably has. Clever.

Now, in fairness, I am using an older 2.x version of MailPoet… I have not switched to the new 3.x line yet (for various reasons). I don’t know if this vulnerability exists in the newer version or not. Nevertheless this seems like a significant flaw with MailPoet!

Supplemental information

From the data I’ve collected, here are the email addresses apparently being targeted:

mpssc@yopmail.com
forensic@evestigator.com.au
alt.r7-2oucp5m8@yopmail.com
jch@linkear.net
support@linode.com
abuse@linode.com
info@somanydesigns.com
caker@linode.com
sales@linode.com

As you can see, there’s a heavy focus on @linode.com addresses, but they are not the only ones. (Admittedly my sample data may be small, since I only have 1 affected site to collect data from.)

Also from the data I’ve collected, here are the IP addresses of the attackers:

82.103.140.214
185.65.135.173

So far all the requests have come from just these 2 IP addresses.

I don’t have any immediate solutions or recommendations yet, but I’ll update this post with further information as I continue to investigate.

Update #1 2018-03-28

After posting this I decided to just ban the 2 IPs the attackers were using.  This stopped it for a little while, but it didn’t take them long to move on to other IPs and other email addresses.

Here are the latest IP addresses: (assuming it’s the same attackers and not copycats)

185.62.205.145
149.36.64.201

This time around the targeted email addresses are mostly @drupal.org addresses. I wonder if these are random attacks, or if someone has a personal grudge?

Rather than continuing to ban IPs, I’ve added the following code to my wp-config.php file to hopefully block these attacks. This is just a temporary, hacky solution until I come up with something better. Since I don’t use the MailPoet list with ID 1, I can just drop all requests to sign up for it:

if ( isset( $_POST['action'] ) && $_POST['action'] === 'wysija_ajax' ) {
	if ( isset( $_POST['data'][6]['value'] ) && $_POST['data'][6]['value'] == 1 ) {
		http_response_code(400);
		die;
	}
}

We’ll see what happens…

Update #2 2018-03-29

So far I have not observed any additional attacking IPs besides the 4 already mentioned above.

I spent some time going through the MailPoet code to try and find the problem. In a nutshell, the problem is this: MailPoet has a system for verifying nonces, but they don’t use it for ajax requests. Requests submitted using ajax can have no nonce at all and will still be processed, as is the case with this attack.

MailPoet does have a system for verifying nonces… if you’re curious, look in the file core/controller.php at around line 87, where there’s a method called requireSecurity(). You can follow it from there. requireSecurity() is called from just about every front end and back end method, but NOT from ajax requests. This seems like a glaring omission to me. If you look at the “Subscribe!” form generated by MailPoet (for example, if you are using the wysija_form shortcode), they don’t even bother to create a nonce with this form! So in other words… ajax requests are just wide open to the internet. :(

What about a solution?

I understand the MailPoet team are no longer supporting the 2.x series, and yet it is still available in the WordPress.org plugins repo. This seems like a pretty serious security problem to me, so I think they should step up and release an update to fix this. Or else they should just remove the 2.x version from the repo altogether. (That’s assuming 3.x doesn’t have the same vulnerability… I haven’t tested it that far yet.)

As a workaround, I am now using the following code snippet in my site’s functions.php file. Basically I’m using the default list (“My First List”, or whatever yours is called) as a honeypot… any attempts to subscribe someone there are automatically blocked. My real subscribers go to other lists. I wish there was a better way to deal with this, but I really can’t find any other way to fix this short of modifying the MailPoet plugin code directly (which, frankly, the MailPoet team should do, not me.)

add_action( 'wp_ajax_wysija_ajax', 'pb_patch_mailpoet_vulnerability', 9 );
add_action( 'wp_ajax_nopriv_wysija_ajax', 'pb_patch_mailpoet_vulnerability', 9 );
function pb_patch_mailpoet_vulnerability() {
	//
	// @programmer_bear, 2018-03-28
	//
	// This code will drop any attempts to subscribe users to the 
	// default MailPoet list ("My First List"), or whatever you call your
	// list with list_id == 1.  Basically it's just a honeypot for spammers
	// at this point, so you'll need to use a different list for your
	// real subscribers.  
	//
	// if anybody else has a better idea, please let me know!
	//
	if ( ! isset( $_POST['task'] ) || $_POST['task'] !== 'save' ) {
		return;
	}
	if ( ! isset( $_POST['data'] ) ) {
		return;
	}
	$drop_it = false;
	foreach ( $_POST['data'] as $data ) {
		if (
			$data['name'] === 'wysija[user_list][list_ids]' &&
			$data['value'] == 1
		) {
			$drop_it = true;
		}
	}
	if ( $drop_it ) {
		http_response_code(400);
		die;
	}
}

 
Update #3 2018-03-29

There is a conversation about this going on the WordPress.org forum, to which the MailPoet team has replied.

Leave a Reply

Your email address will not be published. Required fields are marked *