Authenticated PHP Object Injection Vulnerability in Event List
Since June we have been doing proactive monitoring of changes made to plugins to try to catch serious vulnerabilities. So far that has lead to identifying existing vulnerabilities, newly introduced vulnerabilities, newly introduced vulnerabilities in brand new plugins, and vulnerabilities being fixed. For the first time it has lead to us identifying a vulnerability in a plugin that has been removed from the Plugin Directory. It appears the plugin has been through at least one review by the Plugin Directory team that doesn’t look to have caught this vulnerability. That in itself it is not major concern, but the fact that there doesn’t appear to be any publicly available info on the review process, which others could review and then provide suggestions for improvements that could be made, is more of an concern.
There clearly is room for improvement with review process as we have found that the reviews have failed to make sure that the vulnerabilities that caused plugins to removed have been fixed even when they may already be being exploited and also that the handling of those reviews has caused some developers to abandon plugins or abandon having their plugin in the Plugin Directory. The later happened with the very popular Contact Form DB plugin and lead to a lot of websites being less secure.
Since the developer of the plugin we found this vulnerability in, Event List, doesn’t provide a method to privately disclose vulnerabilities the issues we found with it are now public, so we are providing the details here now even though they haven’t been fixed and the plugin is not currently available in the Plugin Directory.
The plugin makes its main admin page available to anyone with the “edit_posts” capability, which is normally contributor-level and above users:
48 | add_menu_page(__('Event List','event-list'), __('Event List','event-list'), 'edit_posts', 'el_admin_main', array(&$this, 'show_main_page'), 'dashicons-calendar-alt', '22.2'); |
The function that is called when requesting the main page show_main_page() will then call the page show_main(). That function, located in the file /admin/includes/admin-main.php, checks if the user has the “edit_posts” capability (even though that has already been checked for) and then if the specified action is “import” it will run the function show_import():
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | public function show_main() { // check permissions if(!current_user_can('edit_posts')) { wp_die(__('You do not have sufficient permissions to access this page.')); } // TODO: add check_admin_referer to improve security (see /wp-admin/edit.php) // is there POST data an event was edited must be updated // check for actions if($this->action) { switch($this->action) { // actions showing edit view case 'edit': case 'added': case 'modified': $this->show_edit_view($this->action); return; // actions showing import view case 'import': EL_Admin_Import::get_instance()->show_import(); |
That function show_import(), located in the file /admin/includes/admin-import.php, first again checks the user capability:
38 39 40 41 | public function show_import() { if(!current_user_can('edit_posts')) { wp_die(__('You do not have sufficient permissions to access this page.')); } |
Then a few lines down if the POST input “reviewed_events” exists the function import_events() will run:
51 52 | elseif(isset($_POST['reviewed_events'])) { $import_error = $this->import_events(); |
The first thing that function, also located in the file /admin/includes/admin-import.php, does is unserialize the POST input “reviewed_events”, which permits PHP object injection to occur:
303 304 305 | private function import_events() { // check used post parameters $reviewed_events = unserialize(stripslashes($_POST['reviewed_events'])); |
That means that there is an authenticated PHP object injection vulnerability accessible to contributor-level and above users. Nowhere in that code is there any protection against cross-site request forgery (CSRF), so the PHP object injection vulnerability is also exploitable that way. Also looking over the rest of the code handling imports into the plugin there is no CSRF protection, so in also in general the import functionality is susceptible to that type of vulnerability.
Update (October 9, 2017): Version 0.7.11 has been released, which resolves the PHP object injection issue by replacing usage of unserialize() with json_decode() (and replacing related usage of serialize() with json_encode()).
Proof of Concept
With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” be shown, when logged in as a Contributor-level or above user.
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=el_admin_main&action=import" method="POST"> <input type="hidden" name="reviewed_events" value='O:20:"php_object_injection":0:{}' /> <input type="submit" value="Submit" /> </form> </body>
Timeline
- October 6, 2017 – Developer notified.
- October 8, 2017 – Version 0.7.11 released, which resolves the PHP object injection issue.