Base64 Obfuscation Used in WordPress Plugin’s Code That Emails Details of Website to Developer
A month ago we discussed what we found when we looked into the use of base64 obfuscation in WordPress plugins in the Plugin Directory to possibly include a check for that in the proactive monitoring of changes made to plugins to try to catch serious vulnerabilities that we do, after that was used in intentionally malicious code in a plugin. That obfuscation is something that would seem to be in violation of the Plugin Directory’s guidelines:
4. Keep your code (mostly) human readable.
Intentionally obscuring code by hiding it with techniques or systems similar to
p,a,c,k,e,r
‘s obfuscate feature, uglify’s mangle, or unclear naming conventions such as$z12sdf813d
, is not permitted in the directory. Unfortunately, many people use such methods to try and hide malicious code, such as backdoors or tracking. In addition, WordPress code is intended for anyone to be able to learn from, edit, and adapt. Making code non-human readable forces future developers to face an unnecessary hurdle. Minified code may be used, however the unminified versions should be included whenever possible. We recommend following WordPress Core Coding Standards.
What we also noted was that a member of the Plugin Directory team claimed to be monitoring usage of base64:
Any use of Base 64 in plugins actually sends me an email when committed. I read all of those emails and look at the code. 99% of them are totally harmless and reasonable. I’ve continued to read them all for the last 5 years because of that 1% that’s not.
And we noted that it seemed hard to believe they could do that effectively:
Depending on what it is they are reviewing it would seem difficult to believe that they could effectively review it all. Below are the instances of some base64 keywords in just the changes made to plugins in the Plugin Directory last Thursday:
- base64: 3409
- base64_encode: 350
- base64_decode: 202
Something that we ran across last week would appear to show that if there is truly monitoring being done, it isn’t effective.
Phoning Home
We recently introduced a new tool to provide the public to get a quick check done for possible security issues in WordPress plugins. One of the checks we do is for base64 obfuscation like was used in that intentionally malicious plugin. Based on the previous checking we had done we were aware that not everything that was obfuscated was of much concern, so we have been checking on how that is being used in the recent changes being made to plugins in the Plugin Directory again, to get a better idea of how we can refine that check to limit unnecessary identification (so far that has lead to an additional check to limit it from flagging usage of it to create some harmless image files).
When we ran that over changes that were made last Thursday one of the things picked up was the following:
$to = base64_decode( 'cGF2ZWxAeGl2ZXRpLmNvbQ==' ); |
When decoded, that is an email address, which is then set to a variable name “to”, which could indicate an email is being sent to that email address. That is in fact what it does:
$to = base64_decode( 'cGF2ZWxAeGl2ZXRpLmNvbQ==' ); $subject = 'Vidrack Installed'; $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; mail( $to, $subject, $message, $headers ); |
Hiding the email address that a plugin is sending something to seems like it is something that maybe shouldn’t be allowed.
Looking at the full code things get more problematic:
public static function vidrack_activation() { if ( ! get_option( 'vidrack_first_install' ) ) { // Hide columns on vidrack dashboard $user = wp_get_current_user(); $hidden = array( 'vidrack_video_external_id', 'vidrack_video_tag', 'vidrack_name', 'vidrack_email', 'vidrack_phone', 'vidrack_birthday', 'vidrack_location', 'vidrack_language', 'vidrack_additional_data', 'vidrack_capture_url', 'vidrack_custom_data_1', 'vidrack_custom_data_2', 'vidrack_custom_data_3' ); $page = 'edit-vidrack_video'; update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true ); // PREPARE THE BODY OF THE MESSAGE $message = '<html><body>'; $message = '<h1>WordPress</h1>'; $message .= '<table rules="all" style="border-color: #666;" cellpadding="10">'; $message .= "<tr><td><strong>PHP Version:</strong></td><td>" . phpversion() . "</td></tr>"; $message .= "<tr><td><strong>WP Version:</strong> </td><td>" . get_bloginfo( 'version' ) . "</td></tr>"; $message .= "<tr><td><strong>Site URL:</strong> </td><td>" . get_bloginfo( 'url' ) . "</td></tr>"; $message .= "<tr><td><strong>WP Lang:</strong> </td><td>" . get_bloginfo( 'language' ) . "</td></tr>"; $message .= "</table>"; $message .= "<br><h1>Plugins</h1>"; if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); $message .= '<table rules="all" style="border-color: #666;" cellpadding="10">'; foreach ( $all_plugins as $key => $plugin ) { $message .= "<tr style='background: #dcdcdc;'><td><strong>Name:</strong></td><td><strong>" . $plugin['Name'] . "</strong></td></tr>"; $message .= "<tr><td><strong>Path:</strong></td><td><strong>" . $key . "</strong></td></tr>"; $is_activated = 'DEACTIVATED'; if ( is_plugin_active( $key ) ) { $is_activated = "ACTIVATED"; } $message .= "<tr><td><strong>Is activated?:</strong></td><td>" . $is_activated . "</td></tr>"; } $message .= "<tr><td><strong>URI:</strong></td><td>" . $plugin['PluginURI'] . "</td></tr>"; $message .= "<tr><td><strong>Author URI:</strong></td><td>" . $plugin['AuthorURI'] . "</td></tr>"; $message .= "<tr><td><strong>Version:</strong> </td><td>" . $plugin['Version'] . "</td></tr>"; $message .= "</table>"; $message .= "<br><h1>Activated Theme</h1>"; $current_theme = wp_get_theme(); $message .= '<table rules="all" style="border-color: #666;" cellpadding="10">'; $message .= "<tr><td><strong>Name:</strong></td><td>" . $current_theme->get( 'Name' ) . "</td></tr>"; $message .= "<tr><td><strong>URI:</strong> </td><td>" . $current_theme->get( 'ThemeURI' ) . "</td></tr>"; $message .= "<tr><td><strong>Author URI:</strong> </td><td>" . $current_theme->get( 'AuthorURI' ) . "</td></tr>"; $message .= "<tr><td><strong>Version:</strong> </td><td>" . $current_theme->get( 'Version' ) . "</td></tr>"; $message .= "<tr><td><strong>TextDomain:</strong> </td><td>" . $current_theme->get( 'TextDomain' ) . "</td></tr>"; $message .= "</table>"; $message .= "</body></html>"; $to = base64_decode( 'cGF2ZWxAeGl2ZXRpLmNvbQ==' ); $subject = 'Vidrack Installed'; $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; mail( $to, $subject, $message, $headers ); add_option( 'vidrack_first_install', 1 ); } }
The name of the function, vidrack_activation, indicates that it runs during some sort of activation and in fact it runs when the plugin is activated:
register_activation_hook( __FILE__, array( 'WP_Video_Capture', 'vidrack_activation' ) ); |
What the code will do is to collect a number of pieces of information about the website and email them to the developer the first time the plugin is activated. The contents of the email look like this with a fresh install of WordPress:
WordPress
PHP Version: | 7.1.10 |
WP Version: | 4.8.2 |
Site URL: | http://example.com |
WP Lang: | en-US |
Plugins
Name: | Akismet Anti-Spam |
Path: | akismet/akismet.php |
Is activated?: | DEACTIVATED |
Name: | Hello Dolly |
Path: | hello.php |
Is activated?: | DEACTIVATED |
Name: | Video Recorder |
Path: | video-capture/wp-video-capture.php |
Is activated?: | DEACTIVATED |
URI: | https://vidrack.com |
Author URI: | |
Version: | 2.1.1 |
Activated Theme
Name: | Twenty Seventeen |
URI: | https://wordpress.org/themes/twentyseventeen/ |
Author URI: | https://wordpress.org/ |
Version: | 1.3 |
TextDomain: | twentyseventeen |
Violating the Guidelines
While we have seen malicious plugins in the past that have notified the person behind when the plugins is activated, in this case it looks like the developer doesn’t have malicious intent, but this is clearly a violation of one the developer guidelines:
7. The plugin may not “phone home” or track users without their informed, explicit, opt-in consent.
In the interest of protecting user privacy, plugins may not contact external servers without the explicit consent of the user via requiring registration with a service or a checkbox within the settings. This method is called ‘opt in.’ Documentation on how any user data is collected, and used, should be included in the plugin’s readme, preferably with a clearly stated privacy policy.
This restriction includes the following:
- No unauthorized collection of user data. Users may be asked to submit information but it cannot be automatically recorded without explicit confirmation from the user.
- Intentionally misleading users into submitting information as a requirement for use of the plugin itself is prohibited.
- Images and scripts should be loaded locally as part of the plugin whenever possible. If external data (such as blocklists) is required, their inclusion must be made clear to the user.
- Any third party advertisement mechanisms used within the plugin must have all tracking features disabled by default. Advertisement mechanisms which do not have the capability of disabling user tracking features are prohibited.
The sole exception to this policy is Software as a Service, such as Twitter, an Amazon CDN plugin, or Akismet. By installing, activating, registering, and configuring plugins that utilize those services, consent is granted for those systems.
As this occurs when the plugin is being activated there is no consent before that email is sent.
This code was introduced in version 2.0 of the plugin Video Recorder, which was released on September 5th. So there was plenty of time for the member of the Plugin Directory who claims to be monitoring for this type of code to have looked over a notification when that was originally added (there have been several additional version since them, one of them was where we spotted this). Either they are not doing that monitoring or it isn’t effective, which would mean their claim as to percentage of harmless usage of base64 is unlikely to be based on accurate data.
Room for Improvement
This isn’t the first time that member of the Plugin Directory, who is an employee of WordPress founder Matt Mullenweg, has said something that doesn’t seem to be true about the handling of the security of the WordPress Plugin Directory. In August of last year we noted that this person either was misinformed or not telling the truth when making the following claim in discussion about a plugin that was removed from the Plugin Directory after a vulnerability was being exploited in it:
If we pull the plugin for security reasons, then the author usually patches it. If it’s severe enough, we may patch it ourselves after an author does not respond. This is handled on a case by case basis.
In a reply to that, which was subsequently deleted, we noted the inaccuracy of that:
Seeing as we are the people that are reporting many of the vulnerabilities to the Plugin Directory that cause plugins to be pulled, we can say that the reality is that plenty of those plugins never get fixed. In other cases they are not fixed in a timely manner. Also, in some cases you guys put the plugin back in the Plugin Directory even though the vulnerabilities have not actually been fixed, including a recent case where it looks like a hacker had been exploiting a plugin prior to it being removed from the Plugin Directory.
In the case of the plugin mentioned here, a security issue doesn’t get much more severe than this. Not only does the plugin have an easily exploitable vulnerability, but it appears that a hacker is already exploiting it. That is how we became aware of the issue in the first place.
What we also mentioned in post about that, was that the vulnerable plugin that had been the topic of discussion had not been fixed a month after it started being exploited and as of today it still has not been fixed. By any reasonable measure that would be severe enough to warrant being fixed by the Plugin Directory, but it wasn’t, which gets back to the inaccurate information coming from this person.
Having a team that is basing decisions on inaccurate information or misleading the public about the state of the security is obviously a pretty big problem. That seems like thing that would call for at least looking if changes need to be made to the team and for taking input from those outside team. Instead, in that case there was no reply to our comment and it was subsequently deleted. The lack of willingness to have a discussion from the Plugin Directory team isn’t an isolated issue.
The other change that we think would be reasonable to take some action to reduce usage of base64 obfuscation like this, as it would be easy to spot this type of thing and a lot of it looks like it isn’t something that needs to be done. By doing that, it would make it more obvious if someone is using that to try to hide something malicious and make it easier to check on code that currently is obfuscated. It also would just take enforcing the guideline that is already in place.