9 Oct 2023

Another Hacker Targeted WordPress Plugin Still in Plugin Directory Despite Publicly Disclosed Unfixed Exploitable Vulnerability

On Friday, we saw a hacker probing for usage of the WordPress plugin Dropshipping & Affiliation with Amazon across our websites and other websites. As part of keeping track of vulnerabilities in WordPress plugins for our service, we needed to try to figure out what explained that interest. What we found was alarming, though unsurprising. Three days before that the WordPress security provider Patchstack had vaguely claimed the latest version of the plugin contained a fairly serious vulnerability. And yet as of writing, the vulnerable plugin still is available in the WordPress Plugin Directory. So something clearly has gone wrong here. And not for the first time, even very recently.

As with another recent instance of an unfixed vulnerability likely being targeted, it wouldn’t be hard for WordPress to release a fix to stop exploitation. That is something we have offered for years to help them with. They haven’t taken up our offer of help or dealt with it on their own.

Patchstack didn’t provide the information needed to verify their claim. Not providing that information doesn’t appear to have caused a problem for hackers, but reputable security providers need that information to best handle things. That is especially important generally, as we often find that vulnerabilities haven’t actually been fixed despite security providers claiming otherwise. Patchstack also has repeated issues when it comes to that information, including falsely claiming that plugins contain vulnerabilities and also seriously understating the risk that a vulnerability might pose.

Another WooCommerce Extending Plugin is Highly Insecure

Another aspect of the situation which isn’t new, but continues to be alarming is that this plugin is yet another WooCommerce extending plugin lacking basic security. We have been suggesting for years that the developer of WooCommerce, Automattic, could pretty easily help to improve that situation, but they haven’t. Their own handling of security continues to be poor as well.

Authenticated Arbitrary File Upload

In looking into this, it isn’t surprising that hackers wouldn’t have a hard time figuring out where the vulnerability is and how to exploit this.

A quick search of the plugin’s code show two locations where file uploads are handled. One of those is in a function that is AJAX accessible to anyone logged in to WordPress, so that would be an obvious location to match Patchstack’s claim about users with the Subscriber role having access.

The function is named wpas_import_product_from_amazon() and here is the AJAX registration for that in the file /includes/import/wp-amazon-shop-import.php:

14
add_action('wp_ajax_wpas_import_product_from_amazon', array($this, 'wpas_import_product_from_amazon'));

That function will save a file to the website coming from a URL specified by the POST input “image”.

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
public function wpas_import_product_from_amazon() {
	global $wp_error;
	$response = array();
	$sku = sanitize_text_field( $_POST['sku']);
	$title = str_replace("\'", "", sanitize_text_field($_POST['title']));
	$price = wpas_clean($_POST['price']);
	$product_url = esc_url_raw( $_POST['amazon_url']);
	$image_url = esc_url_raw($_POST['image']);
	$product_id = wc_get_product_id_by_sku($sku);
	if ($product_id) {
		$response['status'] = 200;
		$response['success_action'] ='<a href="'.get_admin_url().'/post.php?post='.$product_id.'&action=edit" target="_blank">'.__('Edit','wp-amazon-shop').'</a> | <a href="'.get_permalink( $product_id ).'" target="_blank">'.__('View','wp-amazon-shop').'</a>';
		$response['message'] = __('Already Imported','wp-amazon-shop');
	} else {
		if($this->wpas_capabitlity()){
			//inserting functionality
			$post = array(
				'post_author' => 1,
				'post_content' => $title . '<br><img src="' . $image_url . '" alt="' . $title . '"><br> <br>  <a href="' . esc_url_raw($product_url) . '"  target="_blank">Product Link</a>',
				'post_status' => "publish",
				'post_title' => $title,
				'post_parent' => '',
				'post_type' => "product",
			);
 
			//Create post
			$post_id = wp_insert_post($post, $wp_error);
			//Update term & meta.
			wp_set_object_terms($post_id, wpas_clean($_POST['importing_category']), 'product_cat');
		   // wp_set_object_terms($post_id, get_option('acl_wpas_category_name'), 'product_cat');
			wp_set_object_terms($post_id, 'simple', 'product_type');
 
			update_post_meta($post_id, '_visibility', 'visible');
			update_post_meta($post_id, '_stock_status', 'instock');
			update_post_meta($post_id, 'total_sales', '0');
			update_post_meta($post_id, '_virtual', 'yes');
			update_post_meta($post_id, '_purchase_note', "");
			update_post_meta($post_id, '_featured', "no");
			update_post_meta($post_id, '_weight', "");
			update_post_meta($post_id, '_length', "");
			update_post_meta($post_id, '_width', "");
			update_post_meta($post_id, '_height', "");
			update_post_meta($post_id, '_sku', $sku);
			update_post_meta($post_id, '_product_attributes', array());
			update_post_meta($post_id, '_sale_price_dates_from', "");
			update_post_meta($post_id, '_sale_price_dates_to', "");
			update_post_meta($post_id, '_price', $price);
			update_post_meta($post_id, '_sold_individually', "");
			update_post_meta($post_id, '_manage_stock', "no");
			update_post_meta($post_id, '_backorders', "no");
			update_post_meta($post_id, '_stock', "");
				if ($post_id) {
					// Add Featured Image to Post
					$big_image_url = str_replace('_SL160_', '_SL500_', $image_url);
					$image_url_array = explode("/", $big_image_url);
					$image_name = end($image_url_array);
					$upload_dir = wp_upload_dir(); // Set upload folder
					$image_data = file_get_contents($big_image_url); // Get image data
					$unique_file_name = wp_unique_filename($upload_dir['path'], $image_name); // Generate unique name
					$filename = basename($unique_file_name); // Create image file name
 
					// Check folder permission and define file location
					if (wp_mkdir_p($upload_dir['path'])) {
						$file = $upload_dir['path'] . '/' . $filename;
					} else {
						$file = $upload_dir['basedir'] . '/' . $filename;
					}
 
					// Create the image  file on the server
					file_put_contents($file, $image_data);

There are no security checks done in that code. (The function wpas_capabitlity() called doesn’t do a capabilities check or other security check.)

Quick Fix

There should be multiple security checks in that code, including a nonce check to prevent cross-site request forgery (CSRF) and a limit on what types of files can be uploaded. Another missing security check, which would shut off hackers’ easy ability to exploit this, would be to add a capabilities check to limit access to the intended users. It appears the functionality is only intended to be accessed by users with the “import” capability, which only Administrators normally have. That could be checked by simply adding these two lines to the beginning of the vulnerable function:

if ( ! current_user_can( 'import' ) )
	exit();

Plugin Security Scorecard Grade for WooCommerce

Checked on March 31, 2025
F

See issues causing the plugin to get less than A+ grade

Leave a Reply

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