How to prevent CSRF vulnerability in WordPress plugins and themes
Recently the folks at Secunia contacted me regarding a vulnerability in my plugin Contextual Related Posts.
Contextual Related Posts is a powerful plugin for WordPress that allows you to display a list of related posts on your website and in your feed. The plugin comes with a tonne of options and inbuilt caching that can possibly increase user retention.
This vulnerability was on the settings page of the plugin and opened up the blog to a potential cross site request forgery (CSRF)
The application allows users to perform certain actions via HTTP requests without performing proper validity checks to verify the requests. This can be exploited to change plugin settings and e.g. insert malicious script to pages or posts when a logged-in administrator visits a specially crafted web page.
Since then, I’ve been scouring the web for material on this and you might also want to take a look this article on crunchify for a solution. In this post, I’ll tell you what I did to fix the vulnerability. But first, let’s understand what’s CSRF.
Definitions
Cross-site request forgery
From Wikipedia, Cross-site request forgery, also known as a one-click attack or session riding and abbreviated as CSRF or XSRF, is a type of malicious exploit of a website whereby unauthorized commands are transmitted from a user that the website trusts. Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user’s browser.
Secunia classifies this as a “Less critical” vulnerability and it isn’t often that you’ll notice major CSRF exploits. However, you’ll want to fix your publicly plugin and theme to ensure that users do not open their blogs up to be exploited.
The quickest way to fix a CSRF vulnerability in your WordPress plugin is through the use of WordPress Nonces.
Nonce
From the codex, Nonce is used for security purposes to protect against unexpected or duplicate requests that could cause undesired permanent or irreversible changes to the web site and particulary to its database. Specifically, a nonce is an one-time token generated by a web site to identify future requests to that web site. When a request is submitted, the web site verifies if a previously generated nonce expected for this particular kind of request was sent along and decides whether the request can be safely processed, or an notice of failure should be returned. This could prevent unwanted repeated, expired or malicious requests from being processed.
Nonce is usually included in a hidden HTML form field or as a part of an URL and therefore sent with a request by submitting a form field or visiting a link. If a request is not verified, the web site could generate a new nonce in its response and prompt the user to intentionally confirm the repetition of the request. In WordPress, the response message is “Are you sure you want to do this?” by default.
And, the code…
In my plugin I made two key changes to the code in the admin.inc.php file. This is the file that displays the settings page, takes input from the user and updates the database.
The first change is the addition of this line of code just before the closing form
tag.
[php]
<?php wp_nonce_field(‘crp-plugin’); ?>
[/php]
wp_nonce_field() was a function introduced in v2.0.3 of WordPress and generates a hidden input field in your form. Here’s what you will see if you view the source of the settings page now:
[php]
<input type="<a>hidden</a>" id="<a>_wpnonce</a>" name="<a>_wpnonce</a>" value="<a>NONCEHERE</a>" />
<input type="<a>hidden</a>" name="<a>_wp_http_referer</a>" value="<a>/wp-admin/options-general.php?page=crp_options</a>" />
[/php]
You won’t see NONCEHERE of course, but the actual Nonce.
Adding the nonce field is the first key step, but is essentially useless unless you validate the nonce. This is where check_admin_referer() comes in. You’ll spot on line 20, an additional check that I have added.
[php]
if ( (isset($_POST[‘crp_save’]) ) && (check_admin_referer(‘crp-plugin’) ) ) {
[/php]
WordPress will now check the POST request for the presence of the nonce field. If you’re hitting the save button on the Settings page you’ll never trigger an error. However, if you’re a victim of a CSRF, WordPress won’t be able to validate the nonce and will display “Are you sure you want to do this?”. It won’t process the request and won’t make any changes to the database.
Conclusion and additional reading
I’ve explained to you above one quick implementation of WordPress Nonces which can help prevent CSRF vulnerabilities in your WordPress plugins. Implementing this in your themes is along the same lines.
If you’re plugin is generating a form, then you should use a nonce to validate the input. It’s the first line of defence that WordPress provides you.
Other useful resources
- WordPress Codex on Nonces
- WordPress 2.0.3: Nonces | Mark on WordPress
- Capabilities and Nonces | Wptuts+
- Introduction to Using Nonces in WordPress (YouTube video)
- Improving security in WordPress plugins using Nonces | Prelovac.com
Do you have any other suggestions or additional resources to prevent CSRF? Let us know below.