Nonces are an important part of WordPress security, but they are often misunderstood or misused. They are a key part of what authorizes an HTTP request to your site, which works to keep your code secure.
In this article, you’ll learn what nonces are, what a WordPress nonce is, how they can protect against certain types of attacks, what they can’t protect against, and how to use them.
What Is A WordPress Nonce
Nonces are cryptographic hashes that are used to verify that a request was made by the right person or client. Since nonces are constructed using a cryptographic hashing algorithm — md5, sha1, sha2, etc. — and some sort of secret data, it should be effectively impossible to create a fake nonce.
The word nonce means “number used once.” The one-time use is a key feature of the security of nonces. Even if you intercept a valid nonce, once it’s used it cannot be used again. This makes nonces useful for stopping replay attacks.
That said, WordPress nonces, are not true nonces, and they are valid for 12 hours, or the value of the “nonce_life” filter from when they are created. This means they can technically be used more than once, but only in that 12 hour period. This is an important distinction to keep in mind.
Cross Site Request Forgery
We often think about our WordPress sites as only accepting data by filling out forms — checkout forms, the post editor, user profile editor, etc. and clicking submit. But that is only one of the many ways an HTTP request can be sent to a site.
Once the structure of a form is known, a script can be written to simulate a request to that form. One way to exploit this is to put a fake version of a form on another site that submits back to the original site to insert malicious data.
This is an example of a cross-site request forgery (CSRF) attack. It can be largely avoided by using nonces. Even though the form stays the same, the value of the nonce field will not be predictable.
Every single request to a WordPress site, be it a options form, a request against admin-ajax, the REST API, or anything else that can originate from outside of the site should be protected with a nonce.
A Nonce Is Part Of A System
Keep in mind that validating a nonce, is not enough to ensure that the request to your site is authorized. A capability check will be needed for any request. Consider these three examples of admin-ajax used to save an option from a WordPress plugin option screen:
add_action( 'wp_ajax_save_twitter', function(){ if( isset( $_POST[ 'twitter' ] ) ){ update_option( 'twitter', strip_tags( $_POST[ 'twitter' ] ) ); } }); add_action( 'wp_ajax_save_twitter', function(){ if( current_user_can( 'mange_options' ) && isset( $_POST[ 'twitter' ] ) ){ update_option( 'twitter', strip_tags( $_POST[ 'twitter' ] ) ); } }); add_action( 'wp_ajax_save_twitter', function(){ if( current_user_can( 'mange_options' ) && isset( $_POST[ 'twitter-save-nonce' ], $_POST[ 'twitter' ] ) && wp_verify_nonce( $_POST[ 'twitter-save-nonce' ], 'twitter-save-nonce' ) ){ update_option( 'twitter', strip_tags( $_POST[ 'twitter' ] ) ); } });
The first example allows anyone with a valid cookie for a user of any role to update this option. If you were running that code on your site and I was a subscriber, I could log in to the site to get my cookie and then use the browser console or use cURL in my terminal to change the option.
The second example is somewhat better, but still unsafe. It doesn’t have a nonce, but it does check the right permission for the current user. Without a nonce, this code is still susceptible to CSRF attacks. It is an invitation for a phishing attack.
The third example adds a nonce. This is the right way. If I had an administrator’s cookie in set, and I fell for a phishing attack that would have worked against the first example, the nonce would not validate and there would be no damage.
Nonces work in tandem with authentication checks. You can’t skip one or the other.
Using Nonces
WordPress has a set of functions for working with nonces. The two most important functions wp_create_nonce() and wp_verify_nonce(). Both can take an optional argument for action. You should always set an action unique to your request. That means that your nonce can’t be reused to verify another type of request.
For use in forms, there is a helper function called wp_nonce_field() that lets you create a hidden field with a nonce. This simple form would work with the second example from above:
<form id="twitter-form"> <label for="twitter-name"> <?php esc_html_e( 'Twitter', 'text-domain' ); ?> </label> <input type="text" id="twitter-name" value="<?php echo esc_attr( get_option( 'twitter', '') ); ?>" /> <?php wp_nonce_field( 'twitter-save-nonce' ); submit_button( __( 'Save', 'text-domain' ) ); ?> </form> <script> jQuery( document ).ready(function($){ $( '#twitter-form' ).on( 'click', function(e){ $.post( ajaxurl, { 'twitter-save-nonce' : $( '#_wpnonce' ).val(), twitter: $( '#twitter-name') }) }); }); </script>
This would be used in a menu page that is only accessible to admins. As a result the nonce being used, while it is printed to the screen, would only be printed for admins. The nonce check and the capability check in the admin-ajax callback, combined with WordPress’ verification of the user’s cookie together make this secure.
In this example, we used AJAX for the form submission, but the process would be the same for a synchronous HTTP request.
Nonce On!
That’s really all there is to nonces. Use them, over use them. When in doubt use them. When you don’t think you need one, use one anyway. When someone calls you out for not using one — this happens to us all — add one.
There are two other points I want to make about nonces before I conclude:
One important thing to keep in mind about nonces is that one of the components of a nonce is the current user’s ID, or 0 if they are not logged in. This means that nonces can’t be shared across users. This can get confusing as if a nonce is verified before a user is authenticated, then the correct nonce will not validate. This is a problem that happens when a nonce is used along the WordPress REST API nonce that verifies cookie authentication.
The other important thing to remember is that nonces are susceptible to PHP timing attacks. This is why WordPress users the intentionally inefficient function hash_equals() instead of a simple string comparison. You might be tempted to verify a nonce by generating an expected value and using a string comparison. You should never try and optimize the process of nonce verification.
That’s it. Just a simple introduction into using nonces. I hope in this article you have seen that nonces are simple and vital to keeping your code and the sites running them secure.
4 Comments