Monday, September 21, 2015

What Are WordPress Nonces?

Keeping your WordPress theme or plugin code secure is important to prevent possible attacks from malicious users.

We've previously covered how to sanitize, escape and validate form data in WordPress, as well as improving your WordPress theme quality with the VIP Scanner.

Today we'll look at how nonces (number used once) can also help keep your WordPress themes and plugins secure.

What Are WordPress Nonces?

WordPress nonces are defined as:

… a "number used once" to help protect URLs and forms from certain types of misuse, malicious or otherwise.https://codex.wordpress.org/WordPress_Nonces

Whilst in WordPress a nonce isn't technically a number (it's a hash made up of letters and numbers), it does help prevent actions from being run by malicious users.

WordPress nonces work in two parts:

  • Creating a nonce (hash), submitting it via a form or action, and
  • Verifying a nonce, before accepting form data or running an action.
  • For example, when you go to delete a post in the WordPress Administration Screen, you'll notice the URL contains a _wpnonce parameter:http://127.0.0.1/tuts/wp-admin/post.php?post=542&action=trash&_wpnonce=a03ac85772

    The routine, which deletes a post, will check that post 542 has a nonce value of a03ac85772 before deleting the post. If the nonce does not exist, or does not match the expected value, the post will not be deleted.

    This prevents a malicious user potentially deleting lots of posts. For example, the following wouldn't work, as the nonce belongs to post ID 542:http://127.0.0.1/tuts/wp-admin/post.php?post=642&action=trash&_wpnonce=a03ac85772http://127.0.0.1/tuts/wp-admin/post.php?post=742&action=trash&_wpnonce=a03ac85772http://127.0.0.1/tuts/wp-admin/post.php?post=842&action=trash&_wpnonce=a03ac85772

    Are you sure you want to do this

    Let's now look at how we can implement WordPress nonces in a plugin

    Setting up Our WordPress Plugin

    Let's start with a basic plugin that has its own settings screen. The settings screen has a field, which can be submitted and saved in the WordPress options table.

    Enter the below code in a new file at wp-content/plugins/implementing-wordpress-nonces/implementing-wordpress-nonces.php:

    < ?php /** * Plugin Name: Implementing WordPress Nonces * Plugin URI: http://www.sitepoint.com * Version: 1.0 * Author: n7 Studios * Author URI: http://www.n7studios.co.uk * Description: Example Plugin demonstrating how to implement WordPress Nonces * License: GPL2 */ class ImplementingWPNonces { function __construct() { // Plugin Details $this->plugin = new stdClass; $this->plugin->name = 'implementing-wordpress-nonces'; // Plugin Folder $this->plugin->displayName = 'Nonces'; // Plugin Name add_action( 'admin_menu', array( &$this, 'admin_menu' ) ); } /** * Register the plugin settings panel * * @since 1.0.0 */ function admin_menu() { add_menu_page( $this->plugin->displayName, $this->plugin->displayName, 'manage_options', $this->plugin->name, array( &$this, 'admin_screen' ), 'dashicons-admin-network' ); } /** * Output the Administration Screens * Save POSTed data from the Administration Panel into a WordPress option * * @since 1.0.0 */ function admin_screen() { // Save Settings if ( isset( $_REQUEST['submit'] ) ) { update_option( 'implementing_wordpress_nonces', sanitize_text_field( $_REQUEST[ 'implementing_wordpress_nonces' ] ) ); $message = __( 'Settings saved', $this->plugin->name ); } // Output form ?> < ?php echo $this->plugin->displayName; ?> < ?php if ( isset( $message ) ) { ?> < ?php } ?> < ?php } } $implementing_wordpress_nonces = new ImplementingWPNonces;

    Activate the plugin via the WordPress Administration > Plugins screen, and you'll see a new Nonces menu item displayed:

    Nonces

    Click this, and you'll be taken to the settings screen with a single field:

    Nonces

    Enter any value, click Save, and if everything worked, you'll see confirmation, along with the value you've just entered:

    Nonces

    Demonstrating the Security Flaw

    Enter the following URL into your web browser's address bar (replacing the domain with where you have WordPress installed):http://127.0.0.1/tuts/wp-admin/admin.php?page=implementing-wordpress-nonces&implementing_wordpress_nonces=abc

    Notice what happened? The value was saved as abc, simply by directly accessing a URL and being logged into WordPress:

    Nonces

    Whilst we could use $_POST instead of $_REQUEST in our code (we've used $_REQUEST to make it easier to demonstrate the security issue), this wouldn't help – a malicious user could still, either by themselves or by tricking you into clicking a link – get you to send a POST request to this screen, resulting in the option value being changed.

    This is known as a Cross-Site Request Forgery (or CSRF). It's where a malicious web site, email, application, etc. causes the user's web browser to perform an unwanted action.

    We'll now create and verify a WordPress nonce, to prevent this attack from being possible.

    Securing Our Plugin with a Nonce

    As mentioned earlier, there are two steps to the process: first, we need to create a nonce that will be submitted with our form. Second, we need to verify that nonce when the form is submitted.

    To create a nonce field in our form, we can use wp_nonce_field():

    Retrieves or displays the nonce hidden form field… used to validate that the contents of the form request came from the current site and not somewhere else…

    Add the following code just above our input button:

    wp_nonce_field( 'implementing_wordpress_nonces_save', 'implementing_wordpress_nonces_nonce' );

    wp_nonce_field accepts four arguments – the first two are most important:

  • $action: This determines the specific action we're running, and should be unique. It's good practice to prefix your action with the plugin name, as potentially several actions may run. In this case, we're saving something, so we've used implementing_wordpress_nonces_save
  • $name: This determines the name of the hidden field created by this function. As above, we've prefixed this with our plugin name, therefore calling it implementing_wordpress_nonces_nonce
  • If we reload the settings screen, change our value and click Save, you'll notice the value still changes. We now need to implement a check for the nonce field that was submitted, using wp_verify_nonce( $name, $action ):

    Verify that a nonce is correct and unexpired with the respect to a specified action. The function is used to verify the nonce sent in the current request usually accessed by the $_REQUEST PHP variable.

    Replace the Save Settings section of our plugin's admin_screen() function with code the below:

    // Save Settings if ( isset( $_REQUEST['implementing_wordpress_nonces'] ) ) { if ( isset( $_REQUEST[ 'implementing_wordpress_nonces_nonce' ] ) && wp_verify_nonce( $_REQUEST[ 'implementing_wordpress_nonces_nonce' ], 'implementing_wordpress_nonces_save' ) ) { update_option( 'implementing_wordpress_nonces', sanitize_text_field( $_REQUEST[ 'implementing_wordpress_nonces' ] ) ); $message = __( 'Settings saved', $this->plugin->name ); } else { // Nonce could not be verified - bail wp_die( __( 'Invalid nonce specified', $this->plugin->name ), __( 'Error', $this->plugin->name ), array( 'response' => 403, 'back_link' => 'admin.php?page=' . $this->plugin->name, ) ); } }

    This code performs a few actions:

  • First, it checks that we've submitted something.
  • Then, it checks that our nonce field exists, and if so attempts to verify the nonce's value against the action we're expecting.
  • If the checks pass, the option is updated.
  • If the checks fail, we throw a 403 error with the message "Invalid nonce specified".
  • To ensure our nonces are being created and validated, let's try to access our 'malicious' direct URL again:http://127.0.0.1/tuts/wp-admin/admin.php?page=implementing-wordpress-nonces&implementing_wordpress_nonces=abc.

    If our nonces are implemented and being verified, you'll see the Invalid nonce specified notice:Nonces

    Using Nonces in AJAX Requests

    Say we want to save changes via an AJAX call, rather than reload the entire screen. We can do this with a few code tweaks.

    First, let's load some JavaScript into our plugin, and register an AJAX handler, by adding the following to our __construct():

    add_action( 'admin_enqueue_scripts', array( &$this, 'admin_scripts_css' ) ); add_action( 'wp_ajax_implementing_wp_nonces', array( &$this, 'admin_ajax_save' ) );

    In our class, add the corresponding admin_scripts_css function:

    /** * Register and enqueue any JS and CSS for the WordPress Administration * * @since 1.0.0 */ function admin_scripts_css() { // JS wp_enqueue_script( $this->plugin->name, plugin_dir_url( __FILE__ ) . 'admin.js', array( 'jquery' ), '1.0', true ); }

    For our AJAX call, add the corresponding admin_ajax_save function:

    /** * Saves POSTed settings data * * @since 1.0.0 */ function admin_ajax_save() { // Save option and return 1 update_option( 'implementing_wordpress_nonces', sanitize_text_field( $_REQUEST[ 'implementing_wordpress_nonces' ] ) ); echo 1; die(); }

    Finally, we need to create a new file in our plugin folder for our JavaScript routine, which will POST the data via AJAX when the form is submitted. Let's do this by creating a new file called admin.js, inserting the following:

    jQuery( document ).ready( function( $ ) { $( 'form#implementing-wordpress-nonces' ).submit( function( e ) { // Prevent form submission e.preventDefault(); // Submit form via AJAX $.post( ajaxurl, // Set by WordPress { 'action': 'implementing_wp_nonces', 'implementing_wordpress_nonces': $( 'input#implementing_wordpress_nonces' ).val() }, function(response) { if ( response ) { alert( 'Settings Saved' ); } } ); }); } );

    Reload our settings screen, and if everything worked, when you submit the form, you'll get an on screen confirmation to say that the settings saved.Nonces

    We can reload the settings screen again to make sure the value did update successfully:Nonces

    Again, we now need to implement both the creation and validation of a WordPress nonce, to ensure that CSRF attacks can't happen via an AJAX request.

    For creating nonces for AJAX requests, we can use the WordPress wp_create_nonce function:

    Generates and returns a nonce. The nonce is generated based on the current time, the $action argument, and the current user ID.

    It accepts a single $action argument, so we'd use:

    $nonce = wp_create_nonce( 'implementing_wordpress_nonces_ajax_save' );

    To get this nonce hash into JavaScript's scope, we can use wp_localize_script() to send a JS object. Let's add the following code below the wp_enqueue_script call in our plugin:

    wp_localize_script( $this->plugin->name, 'implementing_wordpress_nonces', array( 'nonce' => wp_create_nonce( 'implementing_wordpress_nonces_ajax_save' ), ) );

    We also need to update our JavaScript file to send this nonce value with the AJAX POST request:

    jQuery( document ).ready( function( $ ) { $( 'form#implementing-wordpress-nonces' ).submit( function( e ) { // Prevent form submission e.preventDefault(); // Submit form via AJAX $.post( ajaxurl, // Set by WordPress { 'action': 'implementing_wp_nonces', 'nonce': implementing_wordpress_nonces.nonce, 'implementing_wordpress_nonces': $( 'input#implementing_wordpress_nonces' ).val() }, function(response) { if ( response == 1 ) { alert( 'Settings Saved' ); } else { alert( 'Invalid nonce specified' ); } } ); }); } );

    The JavaScript file now sends a $_POST variable called nonce with the value of wp_create_nonce.

    Finally, we need to verify the nonce when it's sent in the AJAX request. Update our admin_ajax_save function:

    /** * Saves POSTed settings data * * @since 1.0.0 */ function admin_ajax_save() { // Run a security check first. check_ajax_referer( 'implementing_wordpress_nonces_ajax_save', 'nonce' ); // Save option and return 1 update_option( 'implementing_wordpress_nonces', sanitize_text_field( $_REQUEST[ 'implementing_wordpress_nonces' ] ) ); echo 1; die(); }

    This uses check_ajax_referer, which checks if the given $action (implementing_wordpress_nonces_ajax_save) exists for the given POST field $name (nonce).

    If it fails, -1 will be returned, triggering our JavaScript file to show an alert:

    Nonces

    If it succeeds, 1 will be returned, triggering our JavaScript file to show a success message:

    Nonces

    Conclusion

    We've learnt what a WordPress nonce is, and how WordPress uses nonces to prevent malicious CSRF attacks. By building a basic WordPress plugin, we've shown how a plugin – and WordPress – can potentially be exploited if nonces are not created and verified.

    Using wp_nonce_field() and wp_verify_nonce, we've shown how to validate nonces to prevent CSRF attacks. Finally, we implemented AJAX saving of POST data, and used wp_create_nonce and check_ajax_referer to ensure our AJAX requests are also as secure as possible.

    For the full source code, check out the GitHub repository or download the code directly.


    Source: What Are WordPress Nonces?

    No comments:

    Post a Comment