12 Oct 2018

Vulnerability Details: Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Category Order

From time to time a plugin is closed on the Plugin Directory for an unexplained security issue without the discoverer putting out a report on the vulnerability and we will put out a post detailing the possible vulnerability that lead to that so that we can provide our customers with more complete information on the security of plugins they use.

Earlier this week we discussed a situation where a plugin was closed on the Plugin Directory for a “Security issue.” without any details that would help someone to determine the risk caused by that. That and another situation where a plugin was closed and inaccurate information about the security of it is prominently displayed, lead us to getting back to looking more closely at closed plugins, which we intended to focus on some time ago and it got lost in the shuffle. In looking at that we have found that there are a fair amount of plugins with significant installations, which have been removed due to a claimed “security issue” that we don’t currently have a vulnerability for the latest version of the plugin that was available in the Plugin Directory in our data. That is something we are going to try to work down.

To start working on that we took a look at the plugin Category Order, which has 60,000+ active installations according to wordpress.org and was removed on January 12, 2018. The plugin consists of only one file with only 262 lines of code. That doesn’t leave a lot of room for vulnerabilities. In looking at that, the obvious thing we saw was that user input is not being properly secured when brought in to the plugin. For example, in the function wpguy_category_order_options() the value of the GET input “category_order” is saved to a setting without being sanitized:

111
112
113
114
115
if(isset($_GET['submit'])){
	$options[$childrenOf] = $order = $_GET['category_order'];
	update_option("wpguy_category_order", $options);
	$updated = true;
}

That code also sets the user input to the value of the $order variable. In instances where the value of $order isn’t set with that code it is set to the value of setting that is saved in that code:

107
108
$options = get_option("wpguy_category_order");
$order = $options[$childrenOf];

The value of the $order variable is later output on the admin page:

<input type="hidden" id="category_order" name="category_order" size="500" value="<?php echo $order; ?>">

Since that isn’t escaped you have both reflected cross-site scripting (XSS) vulnerability when the user input is sent as well as the possibility of persistent cross-site scripting (XSS) by the value being saved.

That function is run when the plugin’s admin page is accessed, which since the plugin was last updated 10 years ago is restricted to users with a specified user level (user levels were deprecated in WordPress 3.0, which was released in January of 2010)

15
add_submenu_page("edit.php", 'Category Order', 'Category Order', 4, "wpguy_category_order_options", 'wpguy_category_order_options');

The user level specified, 4, would restrict access to Editor and Administrator users, which both have the unfiltered_html capability, so they would have the ability to use the equivalent of persistent cross-site scripting (XSS) and therefore saving the user input wouldn’t be an issue on its own. The plugin though doesn’t include protection against cross-site request forgery (CSRF), so there cross-site request forgery (CSRF)/cross-site scripting (XSS) vulnerability.

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on a post or page /wp-admin/edit.php?page=wpguy_category_order_options, when submitted as an Editor.

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

http://[path to WordPress]/wp-admin/edit.php?page=wpguy_category_order_options&category_order="><script>alert(document.cookie);</script>&childrenOf=0&submit=Order+Categories

Leave a Reply

Your email address will not be published.