Cross-Site Request Forgery (CSRF)/PHP Object Injection Vulnerability in Ginger – EU Cookie Law
We recently started proactively monitoring for evidence of some high risk vulnerabilities when changes are made to WordPress plugins and if we had more customers we could expand the proactive monitoring to more types of vulnerabilities. One of the types of vulnerabilities we are looking for are PHP object injection vulnerabilities since those are likely to be exploited if hackers become aware of them. Through that we came across a cross-site request forgery (CSRF)/PHP object injection vulnerability in the plugin Ginger – EU Cookie Law.
This vulnerability is a good example of the work that goes in that monitoring. While the first step is automated checking for possible vulnerabilities, we then need to review the code to see if there is in fact vulnerable and small differences can make all the difference in regards to that. In this case before getting to the code potentially vulnerable to PHP object injection there is nonce check, which is intended to prevent cross-site request forgery (CSRF) and depending on who had access to the nonce would also make it so there isn’t a vulnerability. A close look at the code shows that the nonce check is easily bypassed as it only happens if the POST input “submit” is include with a request (in the file /index.php):
13 14 15 | if(isset($_POST["submit"]) && !wp_verify_nonce($_POST['save_ginger_export_options'], 'ginger_export_options')){ return; } |
The next lines are where the PHP object injection vulnerability is (when unserialize() is run on the value of POST input “data”) and the POST input “submit” does not need to have been included in the request for it to run:
16 17 | if(isset($_POST["action"]) && $_POST["action"] == "import"){ if($newconf = @unserialize(stripslashes($_POST["data"]))) { |
The function this all occurs in export() is only accessible to users with the manage_option capability, so without CSRF, there wouldn’t normally be a vulnerability because normally only Administrators have that capability and they can do almost anything they want:
8 | add_submenu_page( 'ginger-setup', "Import Export", __("Import/Export", "ginger"), 'manage_options', 'ginger-export', 'ginger_export'); |
After notifying the developer of the issues they released version 4.1.4, which fixes the issue by checking for a valid nonce and replacing the usage of unserialize() with json_decode() (as well as replacing the usage of serialize() with json_encode()):
16 17 18 19 20 21 | if(isset($_POST["action"]) && $_POST["action"] == "import"){ $nonce = $_REQUEST['_wpnonce']; if ( ! wp_verify_nonce( $nonce, 'save_ginger_export_options' ) ) { exit; // Get out of here, the nonce is rotten! } if($newconf = json_decode(stripslashes($_POST["data"]), true)) { |
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.” to be shown, 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=ginger-export" method="POST"> <input type="hidden" name="action" value="import" /> <input type="hidden" name="data" value='O:20:"php_object_injection":0:{}' /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- August 10, 2017 – Developer notified.
- August 11, 2017 – Developer responds.
- September 6, 2017 – Version 4.1.4 released, which fixes vulnerability.