Improving Nonce Security

WordPress nonces are pretty good at ensuring your ajax calls and form submissions are legit. A nonce is a coded string generated by incorporating the (logged-in) user ID, the $action string, and a timestamp-based “tick” value. The “tick” changes (globally) every 48 hours by default.

The nonce key is unique for each logged-in user. However, what if your site isn’t oriented toward logged-in users? Then the majority of your anonymous users share the same nonce key, valid for 48 hours at a time. Since nonces are passed as hidden form fields, Javascript strings, or query components of a URL, this can present a slight security hole or opportunity for error.

This is probably not worrisome for most sites, but if your particular needs require stricter verification — i.e. a nonce should be unique for each anonymous user — here are a couple of tips:

Use the visitor’s IP address

WordPress filters the (empty) user ID when a user is not logged in. Use that filter to supply the IP address to the nonce hashing function.

add_filter('nonce_user_logged_out','wpcx_noncefield_from_ip');
function wpcx_noncefield_from_ip($arg) {
	if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) return $_SERVER['HTTP_X_FORWARDED_FOR'];
	return $_SERVER['REMOTE_ADDR'];
}

This function prefers the HTTP_X_FORWARDED_FOR value since it’s defined if the site is accessed through a proxy server.

Use the session ID

Since a nonce can be passed as a query arg in a URL, a visitor might access the same URL, with the same nonce value, via bookmark or browser history. If that’s a concern, you might consider this approach.

Hook into the ‘init’ action to start a session if one is not already active, then pass the session ID value to the nonce field filter.

add_action('init','wpcx_initiate_session');
function wpcx_initiate_session() {
	$sid = session_id();
	if (empty($sid)) session_start();
}
add_filter('nonce_user_logged_out','wpcx_noncefield_from_sessid');
function wpcx_noncefield_from_sessid($arg) {
	return session_id();
}

Of course, session ID is just a shortcut to making your nonces unique. If you’re willing to define a session for each user, then you might as well forego nonces and manage your security with session variables exclusively.

Incidentally, you can configure the nonce tick lifetime using the nonce_life filter. By default, it’s set to 1 day (86,400 seconds), and the global nonce “tick” value is calculated by doubling it. It’ll be rare, but someone could encounter an error when that 48-hour value changes; e.g. a user visits a page when it’s 47:59 hours old, then subsequent ajax calls won’t work. Keep in mind, such errors will be more frequent if you shorten the nonce lifetime.