WordPress Nonces Vulnerabilities
Quick Page/Post Redirect Plugin: A Case Study
Quick Page/Post Redirect Plugin has 200,000+ active installs, with version 5.1.5 and older vulnerable to an attacker setting redirects to any URLs in bulk.
And why? All because the developer thinks a 5-byte WordPress Nonce will stop the bulk redirect import functionality from running. Newsflash: It won’t…
Since this particular instance of the vulnerability has been patched, let’s look at how a hole can be poked in code that relies on Nonces to provide “security”. In a blatantly unfortunate way, with disregard for best practices (a series of rants left for another day), the developer decided to allow importing of a bulk redirects export file from any page on the site by hooking their ppr_parse_request_new
function against the init
hook, which happens to run pretty much anytime WordPress does anything.
add_action( 'init', array( $this, 'ppr_parse_request_new' ) );
So to get execution of the function we merely load up any page. Cool.
The next step is to satisfy the following condition:
elseif( isset( $_POST['import-quick-redrects-file'] ) && isset( $_FILES['qppr_file'] ) )
Easy. And thus, we, as an unauthenticated attacker, meet up against the Nonce.
check_admin_referer( 'import-quick-redrects-file' );
The check_admin_referer
function requires two conditions to resolve to true:
- the Referer header has to be set to some Dashboard page (like /wp-admin/)
- the Nonce has to be valid
So the exploit is as simple as running the following request:
curl http://vulnerable.wordpress.site/ -F "import-quick-redrects-file=1" -F "qppr_file=@/tmp/evil_redirects.txt" --header "Referer: http://vulnerable.wordpress.site/wp-admin/" -F "_wpnonce=VALID_NONCE_HERE"
“Ha!”, says any developer putting faith into nonces for security, “You don’t have 10 thousand years to bruteforce the WordPress Nonce.”
Indeed. A WordPress Nonce, by default, is 5 bytes long, that’s 1172812402960 possible nonces to try. At 3-4 requests per second you’re looking at around 10 thousand years to try them all. A nonce also changes every 12 hours (but can be valid up to 24 hours), so we can’t really bruteforce sequentially.
Yet, WordPress Nonces don’t have to be bruteforced into validity. Nonces are not randomly generated; WordPress Nonce generation is completely deterministic and, under certain (common) circumstances, can be predicted with pretty easily.
Let’s look at the wp_verify_nonce
function.
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 );
There are 5 inputs that generate a WordPress Nonce:
-
$i
, which is the current Nonce tick, a predictable number, based on current time (pretty much the same for everyone out there), that is incremented by 1 every 12 hours -
$action
, “import-quick-redrects-file” in this case -
$uid
is 0 for non-authenticated users -
$token
, an empty string for non-authenticated users
The fifth input is the NONCE_KEY
and NONCE_SALT
. And that, my friends, is either predictable (‘put your unique phrase here’ or empty string, WordPress 3.4+ has protection against this) or leaked (leaked and leaked), which is not uncommon.
With all these in place, simply call:
substr( hash_hmac('md5', wp_nonce_tick() . "|import-quick-redrects-file|0|", 'LEAKED_NONCE_KEYLEAKED_NONCE_SECRET'), -12, 10 );
Conclusion
…of course, leaked salts were a HUGE problem of their own, and an adversary with salty hands ? in older versions of WordPress will probably go straight for the keys to the kingdom (i.e. spoofed administrator login cookies), but with a leak on later versions of WordPress and Quick Page/Post Redirect Plugin 5.1.5 and earlier installed putting some evil redirects in place (to a phishing login form, for example) is an effortless one-time try.
WordPress Plugin Developers: please, please, please, always protect your functions using current_user_can. Nonces are not designed to secure access around your functionality, their purpose is to protect against CSRF exploits.
I’m aware of the fact that Quick Page/Post Redirect Plugin has switched maintainer hands recently, and the new developer simply inherited some vulnerabilities along with everything else. I haven’t looked the whole plugin through, this was the only piece of code that I looked at before removing the plugin from an audited site. The plugin may contain instances of more vulnerabilities and should not be considered secure, just as any plugin and theme out there.
Stay safe.