Our Proactive Monitoring Caught a Cross-Site Request Forgery (CSRF)/PHP Object Injection Vulnerability in WP Docs
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. 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 cross-site request forgery (CSRF)/PHP object injection vulnerability we found in the plugin WP Docs. This vulnerability could have allowed an attacker that could get a logged in Administrator to visit a URL the attacker controls, to unintentionally exploit a PHP object injection vulnerability.
What lead us to that was the possibility of a file upload vulnerability in the plugin, but before we got to the code for that we noticed the possibility that a PHP object injection would occur first, in a way that we haven’t seen before, so we focused on that.
The plugin makes its admin page available to users with the “wpdocs-dashboard” capability (which is only given to Administrator-level users by default):
20 | add_menu_page( __('WP Docs','wpdocs'), __('WP Docs','wpdocs'), 'wpdocs-dashboard', 'wpdocs-engine.php', 'wpdocs_dashboard', WPDOCS_URL.'includes/imgs/folder.png' ); |
The function that runs when that page is accessed is wpdocs_dashboard(), which would run the function wpdocs_import_zip() if the POST input “action” is set to “wpdocs-import”
37 38 39 | elseif(isset($_POST['action']) && $_POST['action'] == 'wpdocs-import') { if(wpdocs_file_upload_max_size() < $_FILES['size']) wpdocs_errors(WPDOCS_ERROR_7, 'error'); else wpdocs_import_zip(); |
In the function wpdocs_import_zip() , which is located in the file /includes/wpdocs-import.php, a zip file will be unzipped and then if either of two files exists in that zip file they will be run through the unserialize() function, which can cause PHP object injection:
56 57 58 59 60 61 62 63 | move_uploaded_file($_FILES['wpdocs-import-file']['tmp_name'], $wpdocs_zip_file); $zip_result = wpdocs_unzip($wpdocs_zip_file, sys_get_temp_dir()); if(is_array($zip_result)) { if(file_exists(sys_get_temp_dir().'/wpdocs/wpdocs-list.txt')) { $wpdocs_list_file = unserialize(file_get_contents(sys_get_temp_dir().'/wpdocs/wpdocs-list.txt')); } else $error = true; if(file_exists(sys_get_temp_dir().'/wpdocs/wpdocs-cats.txt')) { $wpdocs_cats_file = unserialize(file_get_contents(sys_get_temp_dir().'/wpdocs/wpdocs-cats.txt')); |
In some other instances we have found that possible PHP object injection vulnerabilities have not been exploitable due to escaping, so we were not sure if something like that might happen with the code used to get the contents of a file. But a quick test with our plugin for testing for PHP object injection showed that this was exploitable.
There was no protection against cross-site request forgery (CSRF) in any of the code leading up to the code that does the serialization, allowing for exploitation.
After we contacted the developer they released version 1.1.8, which fixes the vulnerability by commenting out the line that calls the function wpdocs_import_zip():
40 | //wpdocs_import_zip(); |
Proof of Concept
With our plugin for testing for PHP object injection installed and activated, use the following proof of concept to upload a zip file containing a file named wpdocs-list.txt that contains ‘O:20:”php_object_injection”:0:{}’ as it content and the message “PHP object injection has occurred.” will be shown, when logged in to WordPress 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=wpdocs-engine.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="action" value="wpdocs-import" /> <input type="file" name="wpdocs-import-file" /> <input type="submit" value="Submit" /> </form> </body>
Timeline
- April 16, 2018 – Developer notified.
- April 16, 2018 – Developer responds.
- April 16, 2018 – Version 1.1.8 released, which fixes vulnerability.