Cross-site Request Forgery (CSRF) Vulnerability in Yoast SEO
One of things we think highlights the poor security of WordPress plugins is how often reviewing a report of a vulnerability points to other security issues in a plugin. Recently the security company Wordfence released an advisory for the Yoast SEO plugin for what seems to be a rather minor issue. Logged in users could access several functions of Yoast SEO that they were not normally intended to have access to, including exporting the plugin’s settings.
Since Wordfence had not included a proof of concept that we could use to verify the vulnerability and determine what versions were vulnerable, we needed to create that ourselves. To do that we started by looking at the changes made in version of the plugin that was suppose to have fixed the issue, 3.2.5. The only relevant changes we could find were these:
Those changes stop the code from running if the request is made by someone that doesn’t have certain capabilities. We had expected to also see a change related to a nonce, which is unique value that is used to prevent cross-site request forgery (CSRF). The vulnerability with the export function should not have been possible without a problem somewhere in the relevant nonce being generated and checked, since a user who could not get to the settings page would not have access to that if it everything was done correctly and should not be able to do an export. So what was going on?
Looking the relevant page of the plugin, /wp-admin/admin.php?page=wpseo_tools&tool=import-export, a nonce actually exists:
<script>// <![CDATA[ var wpseo_export_nonce = '430e1e5067'; // ]]></script> |
When the button to do an export is clicked that nonce is then passed along in the request:
a("#export-button").click(function(b){a.post(ajaxurl,{action:"wpseo_export",_wpnonce:wpseo_export_nonce,include_taxonomy:a("#include_taxonomy_meta").is(":checked")},function(b){b=JSON.parse(b); |
We then did a search through the plugin’s code and found that the nonce is never checked.
We figured that check of the nonces validity probably had existed and got removed during a code change and that looks to be what happened.
In version 2.1.1 the check happens on the first line of the function wpseo_get_export:
function wpseo_get_export() { check_ajax_referer( 'wpseo-export' ); $include_taxonomy = ( WPSEO_Utils::filter_input( INPUT_POST, 'include_taxonomy' ) === 'true' ) ? true : false; $export = new WPSEO_Export( $include_taxonomy ); wpseo_ajax_json_echo_die( $export->get_results() ); } |
In the next version, 2.2, that line has been removed and the next line has been changed.
function wpseo_get_export() { $include_taxonomy = ( filter_input( INPUT_POST, 'include_taxonomy' ) === 'true' ); $export = new WPSEO_Export( $include_taxonomy ); wpseo_ajax_json_echo_die( $export->get_results() ); } |
Since the other changes made were not radical it does seem hard to understand how the line could have been lost, possibly indicating that the developer doesn’t have the best practices in place in reviewing changes being made to the plugin.
Version 2.2 was released on June 10 of last year, so the vulnerability has been there for 11 months.
There isn’t much risk from this vulnerability since you would have to get someone logged in as admin to visit a URL to cause the file with export of the settings to be created. That file doesn’t look like it normally would contain sensitive data. At that point you also wouldn’t have access to the export file, that would require taking advantage of the other security issue we found while looking into this.
We notified the developer of the issue on Friday and yesterday they informed us that they “decided the severity of the issue is too low to do a patch release” and “will of course fix the issue before the next release (3.3) which is currently scheduled to be released on June 14th”. They also said they “would appreciate it if you could hold off on publishing about this before the next release.” We informed that we would not be doing that due to the fact that the issue is fairly obvious based on the previous security advisory from Wordfence and due to its low severity. In response the developer said “So much for responsible disclosure.”. For the record we believe in reasonable disclosure, not responsible disclosure. In this case, either this is a low severity issue and its disclosure isn’t a big issue or disclosing it would be a big issue and it should have already been fixed by now.
It also worth noting that the fact that Wordfence’s security researchers missed this issue speaks poorly of their security knowledge, something that might not come as a surprise since that company doesn’t seem to very knowledgable about security in general.
Update June 14, 2016:
Yoast SEO 3.3 was released today and the vulnerability was not fixed, despite the developers having told us it would be.
Proof of Concept
The following proof of concept will cause the export to occur despite
the nonce not being included, when logged in as admin.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action=" http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST"> <input name="action" type="hidden" value="wpseo_export" /> <input type="submit" value="Submit form" /> </form> </body> </html>
Timeline
- 5/6/2016 – Developer notified.
- 5/10/2016 – Developer indicates the vulnerability will be fixed in version 3.3.
- 6/21/2016- Version 3.3.2 released, which fixes vulnerability.