One-time Numbers (nonce)
In the introduction it was mentioned that there are two approaches to checking permissions: "User Permission Check" and the complementary "One-time Numbers".
"One-time Numbers" are a type of token in WordPress - a randomly generated string of 10 characters, for example bec698e7ea, which can be repeated within a short period of time (24 hours). That is, the number can be created and verified only within 24 hours; after that, the same operation will return a different "number".
Such numbers are called "Nonce" in WordPress - "Number used ONCE" and are used for additional protection of GET and POST requests. They can be perceived as secret signs to ensure that the action is authorized.
For more details, see the description of the function wp_create_nonce()
Example of Nonce Operation
We logged into the admin panel, in the settings, upon entering, a form was generated where one of the fields is such a "nonce value". We changed the data and clicked the "save" button. When processing the data, in addition to checking access rights (current_user_can()), WordPress checks whether the nonce number matches. If the nonce does not match, the data saving request will fail.
We do not notice these nonce checks because usually everything happens quickly. But if, for example, you go to the settings page in the admin panel, wait 24 hours, and then try to save the data, the changes made will not be saved because the nonce check will not pass.
Why is the "one-time number" check needed?
Let's imagine that an unauthorized user without permissions found a vulnerability in a plugin. For example, he knows that there is a link, clicking on which the administrator, unknowingly, will change some setting (option) on the site, as needed by the hacker. For example, a hacker's JavaScript (specified in the link) will be written into the settings, which will then execute when the same administrator visits another page of the site.
There are various ways to make the administrator's browser follow such a link.
For example, create a page and use such code in it:
<img src="http://example.com/wp-admin/post.php?post=123&action=trash" />
Or, for example, write a letter to the site administrator, in which to slip an externally harmless link...
An authorized user (admin) will click on the link, and as a result, the hacker's JS code will execute, which, for example, will send the authorization data. And so, an unauthorized hacker has become the administrator of your site...
To prevent such tricks, "one-time numbers" (nonce protection) exist.
Since the nonce number expires, if you add this number to the URL or as a form parameter and then check it, the URL or form will only be valid for 24 hours. After that, requests created by the URL or form will simply fail the check. Thus, if a hacker makes you click on a known link, that link will most likely no longer work because it will not pass the "one-time number" check.
Nonce Functions
To manage one-time numbers in WordPress, there are special functions. The main ones are:
- wp_nonce_field( $action, $name )
- Creates an input field with a one-time number for the form.
- wp_create_nonce( $action )
- Simply creates a one-time number as a string.
- wp_nonce_url( $url, $action )
- Adds nonce to the given URL as a query parameter
?_wpnonce=9d6bd884a1 - wp_verify_nonce( $nonce, $action )
- Checks the one-time number.
- check_ajax_referer()
- Checks the Ajax request for nonce code compliance. Stops script execution through die if the check fails.
Example of Using Nonce Protection
This is an example from the section user permission check, with nonce check added.
When creating a link, we use wp_create_nonce() to add a one-time number to the link:
// function outputs a link with a nonce number for deleting a post
function frontend_delete_link() {
// check permissions
if( ! current_user_can( 'edit_others_posts' ) ) return;
$url = add_query_arg(
array(
'action' => 'frontend_delete',
'post' => get_the_ID();
'nonce' => wp_create_nonce( 'nonce_frontend_delete' ),
),
home_url(),
);
printf( '<a href="%s">Delete</a>', esc_url( $url ) );
}
The argument passed to the function ensures that the one-time number is unique for that specific action.
Then, when processing the request, we check the one-time number:
if ( isset( $_REQUEST['action'] ) && 'frontend_delete' === $_REQUEST['action'] ) {
add_action( 'init','frontend_delete_post' );
}
function frontend_delete_post(){
// check permission
if( ! current_user_can( 'edit_others_posts' ) ) return;
// check nonce
if( ! wp_verify_nonce( $_REQUEST['nonce'], 'nonce_frontend_delete' ) ) return;
// Post ID
$post_id = ( isset( $_REQUEST['post'] ) ? get_post( (int) $_REQUEST['post'] ) : false;
// no post ID
if( empty( $post_id ) ) return;
// Delete post
wp_trash_post( $post_id );
// redirect to admin
$redirect = admin_url( 'edit.php' );
wp_safe_redirect( $redirect );
exit;
}
Referer Check
In addition to Nonce, there is also a referer check. It complements nonce protection. It is an independent check and works like this: a parameter is passed from the form where HTTP_REFERER is specified; when processing the form, this parameter is checked, and if the request did not come from the expected page, the check fails.
For referer checking in WordPress, there are 3 functions:
- wp_referer_field() - creates a referer field
- check_admin_referer() - checks the referer field
- wp_nonce_field() - by default also creates a referer field for the form, along with the nonce field.
Conclusion
When performing any data transformation operation, such as saving plugin settings or deleting plugin data, it is always necessary to ensure that the user has permission to perform the operation (permission check) and that the executed request has not expired (nonce check).