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(); |