WordPress’ implementation of nonces was one of my first encounters with cryptographic protections in software development. It’s too bad WordPress nonces aren’t really nonces in the first place …
A nonce is a “number used once.” It’s intended to be a random, unpredictable value that is tracked and required by the application to prevent replay-style attacks.
WordPress nonces are, admittedly, not numbers and not used once. They’re not nonces at all, but allegedly serve the same purpose in the application. Each nonce is a predictable string created by hashing the current user’s ID with the name of the action they’re trying to perform, the ID of their current authentication session, and the current nonce “tick.”
In WordPress, the nonce click “ticks” every 12 hours. This means, for any given user/action/session combination you will create the same nonce every single time for 12 hours.
In addition, WordPress considers both the current nonce and the immediately previous nonce to be “valid” for performing nonce-protected operations. This means any nonce generated by WordPress is in fact valid for 24 hours.
The Problem with WordPress
This issue here might not be immediately obvious. Considering this is the way WordPress has used “nonces” for so many years, it’s an easy thing to miss.
The primary point of a nonce is to prevent replay attacks – someone intercepting a request cannot reuse a previously used nonce.
In WordPress, nonces are expected to be reused. Many times. Over several hours!
While WordPress nonces might serve as useful CSRF tokens in certain circumstances, they fall down horribly when providing actual protection against replay attacks in the WordPress admin.
A Path Forward?
I spent some time working with a consulting client on “true nonces” for WordPress some months ago. The system I put in place added a table to the database and tracked any nonce as it was used. Each nonce was a truly random number, created with PHP’s random_bytes() method, and bound to a specific user, authentication session, and action. The thing is, I didn’t need to validate each nonce. I just tracked the nonce in the database and, if a subsequent request within a certain timeframe used the same random number, I’d reject the request.
This is how a nonce is meant to be used. 1 Unfortunately, it broke everything. My client at the time was using Beaver Builder, which in itself is a fantastic platform, but the system also uses a WordPress nonce to protect AJAX actions in the admin. Since I only allowed a nonce to be used once, my client could only save one setting at a time. The nonce issued for that first AJAX request would be stored in the database and, the next time a save was triggered, the request would fail due to nonce reuse.
This shows us the fatal flaw with WordPress’ implementation. It also shows us what we’ll need to do within WordPress if we ever want to support true nonces. We need a new table. We need a new verification routine. And we need some way to reconcile the fact that WordPress has misused the term “nonce” for over a decade in the first place!
The path forward requires a major paradigm shift in WordPress development. We need to add true cryptographic operations. We need to (potentially) rename a horrible misnomer that, frankly, is setting up thousands of developers to make mistakes in their future. And we need to help educate thousands of developers about proper security controls in their APIs. It’s possible, but the path forward looks incredibly steep from here.
- Tracking the reason a nonce was created was a routine I added to try to marry a true nonce implementation with WordPress’ non-nonce pseudo-CSRF implementation. I’d track the fields to override wp_verify_nonce(), but that’s also how I identified the issues in the first place. ↩