breaking into a wordpress site without knowing wordpress/php or infosec at all

Update: stats suggest this post is getting viral. so... some points now:

This is a post about how I tried and broke into my college's wordpress installation without having any prior knowledge of wordpress/php and without any experience with hacking web-servers. The attempts were spread out over a month, but effectively totaled a day maybe. I learned a lot of things while doing the research part which accounted to most of my time, though. Here I'd share some of the relevant details and how I went along doing this.

My college has a website. And I simply wanted to find a vulnerability, exploit it, and post troll pictures on the front-page. While that's not a very nice thing to do, but that's irrelevant here. I knew the site ran wordpress, as my teacher vaguely recited in the webdev class.

So I googled "how to hack wordpress", and after a couple results, I found a tool called "wpscan", which examines a wordpress site and shows you a list of possible vulnerabilities.

It isn't that simple though. The scan doesn't automatically attack the website and magically understands what a script kiddie wants to do. It just puts out a list of possible issues.

Anyways, it gave me some pretty good info. The interesting bits for our story are...

...
[+] WordPress version 3.9.11 identified from advanced fingerprinting
...
[+] Name: revslider
 |  Location: http://xxx/wp-content/plugins/revslider/

[!] We could not determine a version so all vulnerabilities are printed out

[!] Title: WordPress Slider Revolution Local File Disclosure
    Reference: https://wpvulndb.com/vulnerabilities/7540
    Reference: http://blog.sucuri.net/2014/09/slider-revolution-plugin-critical-vulnerability-being-exploited.html
    Reference: http://marketblog.envato.com/general/affected-themes/
    Reference: http://packetstormsecurity.com/files/129761/
    Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1579
    Reference: https://www.exploit-db.com/exploits/34511/
    Reference: https://www.exploit-db.com/exploits/36039/
[i] Fixed in: 4.1.5
...

Some other vulnerabilities it listed were about XSS and shell uploads etc. I read about those and found that they won't help me here because the college site didn't accept user-content or comments at all. It was just a host of information.

So anyways, here we have the wordpress version used by the site, and a vulnerability which lets me read arbitrary files from the server, provided I:

The first issue is a bummer. I can't get directory listings or stuff like that either. The server is running windows, not linux, so no way to get hints from stuff like terminal history etc. What we can do is try known paths for certain known files and hope for the best.

The links given as references have an interesting proof of concept of the attack. You can get a file called wp-config.php. I grabbed this file by requesting the following url from Firefox and found that it had database credentials, and some large cryptographic tokens. Wow!

http://mycollegewebsite/wp-admin/admin-ajax.php?action=revslider_show_image&img=../wp-config.php'

The database (MySQL) was not listening to outside connections, so the credentials were useless to me. I googled a bit and thought of detecting phpmyadmin hoping to get in that way, but that didn't work either. As I discovered later, phpmyadmin wasn't used at all (and isn't really necessary either for setting up wordpress).

So now I had those large crypto tokens which had no meaning to me. So I googled what those tokens meant, and how I can exploit those, but that resulted in some more big crypto articles which didn't make any sense to me.

Instead, I resorted to reading wordpress' source code. It is, (surprise!) written in PHP, and the language itself is pretty simple. Documentation is readily available as well.

According to https://github.com/WordPress/WordPress/releases/tag/3.9.11, 3.9.11 refers to the code at commit be55c7. Searching for some of the interesting constant names holding those tokens, like "AUTH_KEY" and "SECRET" in that repo and scrolling along a bit takes us to an interesting file called "pluggable.php", where this mighty function wp_salt seems to be using all of those tokens.

Salt refers to a random and unique string of data which is added to passwords and other password like stuff before they are passed through hashing functions. This makes it more difficult to retrieve the original password from the hashed value for an attacker. Woooh, this is getting better!

The code itself for that function is all mysterious looking to me. The only and only other place where that function is called is wp_hash which is apparently the hashing function here. It uses another function hash_hmac which is a built-in PHP function related to cryptography.

I found that trying to retrieve the password in plain text is gonna be extremely unlikely, specially seeing that I don't even have the hashed password. What I can do instead is hijack a session of the admin user.

Sessions are based on cookies. Cookies are small tokens (so many tokens everywhere) which the server sends to the client's browser. The browser stores it and sends it back to the server along with every further request. The server detects it and knows that this request comes from the guy who owns a particular session. Sessions have bags of data associated to them, stored on the server. Usually if a session cookie is matched, the application assumes you are logged in. I assumed wordpress does that too.

So if I can somehow get the cookie associated to a valid session for the admin user, I can get access to the admin account itself! Woooh, really cool realizations here!

The same file contains another magic function whose name suggested that it created these cookies: wp_generate_auth_cookie. So apparently it takes a user_id, expiration time for the session, and a scheme which I don't know what it refers to. But anyways, lets see how it generates the cookie and what it does with it.

Annotating the code for that function with comments and removing useless bits, we get...

$user = get_userdata($user_id); // get user data from the database, likely
$pass_frag = substr($user->user_pass, 8, 4); // get 4 characters from the hashed password from the 8th offset
$key = wp_hash($user->user_login . $pass_frag . '|' . $expiration, $scheme); // use that magic hashing function to generate a key
$hash = hash_hmac('md5', $user->user_login . '|' . $expiration, $key); // do something with the key again to get another key like thingy
$cookie = $user->user_login . '|' . $expiration . '|' . $hash; // concatenate username, expiration time and the key like thingy to get the cookie

return apply_filters( 'auth_cookie', $cookie, $user_id, $expiration, $scheme ); // apply some filters to this and return the filtered value

so... we need 4 characters from the hashed value of user's password, an expiration time, and the username first. I found that there were no filters attached for 'auth_cookie', so we can ignore the last line. It just returns the $cookie variable as is. The username seems to be the common one called "admin" which I got by hit and trial on mycollegesite/wp-login.php which tells you if a username exists or not by simply attempting a login.

wp_hash relies of wp_salt, and wp_salt relies on the tokens we got. So if can understand what all these functions do and if we give them all the data we have and also somehow get the 4 characters from the hashed password, we can get our cookie! Closer than ever, we are!

But how will the server know if the cookie we generated is a legit one or not? How does it know if someone generated that cookie manually or it was issued by the server? It must store them somewhere to check back later, right? Actually... it doesn't. The same file pluggable.php has the function wp_validate_auth_cookie which does no such checks. It simply checks if the format of the cookie value is valid or not by checking against those tokens and the 4 characters from the hashed user's password.

In some technical terms, Wordpress uses stateless authentication tokens. It relies on the secrecy of the tokens stored in wp-config.php which got leaked. So we can fool the server easily.

So before trying to get those magic 4 characters by guessing, I decided to understand the magic functions wp_salt and hash_hmac. I tried and understood some stuff, but not everything. I realized that since they are deterministic functions, instead of understanding their source, I can just run those functions on my local machine and use their output!

I copied that file in its entirety, started removing the useless part, and changed some references so I can run it on my machine. I gave it those tokens I got from wp-config.php and a random timestamp and random 4 characters, and tried validating the output of wp_generate_auth_cookie against wp_validate_auth_cookie.

I realized that the cookie it sets also has a name associated to it. The name of the cookie is of the form <siteurl>_<md5 of site url>. Don't know why it has to be like that or what extra security it brings, but md5 is another hashing function. Well ok, we do have the site url ofcourse. The code in the end looked like this with irrelevant bits stripped:


define('AUTH_COOKIE', 'stuff I retrieved from the server');
// more of those

define('VICTIM_SITE_URL', 'mycollegewebsiteurl');
// some other similar "configurations" used in functions below

function wp_generate_auth_cookie($user, ...) {
    $pass_frag = $user->pass_frag; // we'd pass a user object instead of user id and let the rest of the code do the magic without me needing to understand it
}

function wp_hash(...) {...};
function wp_salt(...) {...};
// other stuff which looked relevant from the pluggable.php file

function createUser($pass_frag, $name='admin') {
    $user = new stdClass();
    $user->user_login = $name;
    $user->pass_frag = $pass_frag;
}

echo wp_generate_auth_cookie(createUser('xxxx'), ...);

So after some tries of generating the proper cookie value with the generate function and feeding it to the validate function, I finally got them to match. Phew! Only 4 characters left.

So I thought I'd try my luck and enter random 4 letter combinations for the password fragment. I googled and found an extension for Firefox which lets me attach any cookie value for any site. I attached the cookies generated by my script and navigated to my college's website... and it didn't work. My guess of the 4 characters was incorrect. I tried a couple more times but luck won't shine so easily.

I started brute-forcing the 4 characters and attached the login-attempt logic to my script using php-curl. After about 60k attempts, I stopped it. Because... wait a minute, the password hash is in the database, and the database is on the server, and I can read files on the server!

Getting the location of the database files was easier than I thought. Googling mysql windows database file location returns plenty results. I found that the exact location is stored in a file called my.ini which is stored in C:/ProgramData/MySQL/MySQL Server 5.6/my.ini (or 5.5 or whatever version it is running). I found that most Windows server instances ran 5.6 or 5.5, so I tried with both of those. Retrieving the my.ini file was a bit tricky but not that much. I modified the original request url for wp-config.php a bit...

http://mycollegewebsite/wp-admin/admin-ajax.php?action=revslider_show_image&img=../../../../../ProgramData/MySQL/MySQL%20Server%205.6/my.ini'

and it returns that file! That number of .. were enough to make it traverse back up to the root :D

The file contained this (among other things):

# Path to the database root
datadir="C:/ProgramData/MySQL/MySQL Server 5.6/data\"

So we know the root of the data directory now. Since we can't do directory listings, we'd have to do some more guesswork to grab the database files. I found that the structure of this folder is rather simple. It contains a folder for each database by the database name, and inside them it contains a file with name <table name>.ibd which contains the table data. The database name was in the wp-config.php file I got, and wordpress stores the user information in a table called wp_users.

So... we send a request to..

http://mycollegewebsite/wp-admin/admin-ajax.php?action=revslider_show_image&img=../../../../../ProgramData/MySQL/MySQL%0Server%205.6/data/database-name-from-wp-config/wp_users.ibd

and oh gosh, Firefox starts downloading the database!

I thought I can just open the file in vim or something and it would show me the data, but... no. Apparently these files are stored as compressed data with a db engine called InnoDB. It took me some hardcore googling to figure out how to retrieve the data from that, but anyways, I did that and learned some SQL along the way. Ran select user_pass from wp_users where user_login='admin'; on the table, and I got what I needed!

I extracted the 4 characters from the correct offset and fed them to the script. To my freaking surprise, it worked! I tried it with Firefox and that extension and now I finally had access to the admin dashboard! :)


Conclusion

So... while this is not the absolute description of my journey, it is more or less correct. Some factual information I did have previously but that doesn't change the fact that a determined and curious "script kiddie" can still easily google all that.

Lessons here are not just one.