Nico's hax0r blog

How to take ownership of a forum.

UPDATE: I’ve patched this exploit using my newly acquired admin powers. It will no longer work. So don’t waste your time! ;p

I’ve been on this vBulletin message board for quite a while now, and we have a problem there; The administrator left us for dead. He hasn’t been online for… way too long, and we need some changes on the site.

I figured this was an appropriate moment to get my hax0r skills involved. I’m going to explain how I did this, but I’m not going to get into every detail, as I don’t actually want anyone on this site to try it out. If you have some SQL/PHP background, you’ll probably figure this out by yourself. You still shouldn’t try this, though. Something like a missing WHERE clause could make all user accounts unusable. All of them…

Having that said,… let’s get started! What I need to accomplish is to get admin rights, so I can make the changes we need. Luckily, the forum has some custom add-ons, made by the admin himself. I thought this was a good place to start looking for exploits. There’s one part where you can change other people’s user title (a little text which is being displayed beneath the user’s screen name).

I just entered a single quote ( ’ ) to see if the user input was being escaped properly, and… jackpot! I received this error:

Don’t EVER show errors like this to the user! Do you realize how useful this information is to  me? I can see the table name (no table prefix), and the field names. I can see exactly what’s going on behind the scenes, and how to exploit this vulnerability.

Could this be any nicer? I don’t think so. This means I now have full “UPDATE” access to the “user” table.

The text input field which I’m using was limited to 21 characters, but luckily only on the client-side (big big mistake). I opened up Firebug and removed this attribute from the text field: maxlength=”21”… and the limit was gone. If you want to limit the input length, don’t trust the user. Do it on the server-side as well. (Hint: substr)

So how can I use this exploit now? I can write to the “users” table only, that means I can change my usergroup ID to “6” (which is vBulletin’s default ID for administrators). That would gain me access to the admin panel, but it will only give me a very limited amount of options (which are worthless to me). The real admin himself needs to give the user all wanted privileges, and this can’t be done in the “users” table alone. Ideally, I’d need access to his account so I can do this myself.

With this exploit, I could change the admin’s password, but needless to say, he’d realize that the next time he tried to log on. So what if I copy his password, (which by the way is an MD5 hashed string), to a temporary place? This way I could restore it when I’m done with the changes, as if nothing ever happened.

I only have “UPDATE” access to the “users” table, so I need a field in there that can hold at least 32 characters. I figured the “msn” field is just perfect for that (it holds up to 100 characters). Although, the “yahoo” and “skype” fields would have done it too:

(Having a local copy of vBulletin comes handy when investigating.)

So how can I SELECT his password, and copy it to my “msn” field? Remember that we have to do all tasks with just one single UPDATE query. And usually, you can’t use UPDATE queries with SELECT sub-queries if both access the same table. But there’s a trick too.

UPDATE `user` SET `msn` = (
SELECT `password` FROM (
SELECT `userid`, `password` FROM `user` WHERE `userid` = 1
) AS `x`
WHERE userid = 1
)
WHERE userid = xxxx

If you want some good piece of advice, don’t forget the WHERE clauses (none of them)! I accidentally selected 50+K users and temporarily crashed the MySQL server.

Sweet! Now my MSN field holds his hashed password, which I don’t want to (and can’t) reverse. But I need it so I can put it back later when I’m done, so he won’t notice I “borrowed” his account.

Okay, I have his hash, now I need to change his password. In pseudo-code, vBulletin does the hashing like this: md5 ( md5 ( password ) salt )

For security reasons, every user has their own salt stored in their database row. It’s used to create different MD5 hashes so no one can find the original password using rainbow tables. But in this case, it doesn’t secure anything. We can just grab this salt again and create a new password with it. We don’t even need to know what the actual salt is.

We can do this with native SQL.

UPDATE `user`
SET `password` = MD5(CONCAT(MD5('new pass'), `salt`))
WHERE `userid` = 1

Easy… Now that I have set my own password, I can just log in with his account, using his original username and the new password I just created, and give myself all admin rights. After doing that, I simply change his password back:

UPDATE `user`
SET `password` = 'The MD5 value from my MSN field'
WHERE `userid` = 1

This might be obvious, but you can get the hash by going to your profile and reading the value in the MSN field.

Done… I now have full admin rights, and the real admin won’t notice unless he takes a closer look. But then it’s gonna be too late anyway! ;)

UPDATE:

Since I have access to the plug-in system now, I could create and install my own ones. And thus, allowing me to upload/download files, or anything else I wanted to do. I have complete control…

ALTERNATIVE WAY:

I did not actually try this method, but it should work as well.

Users are identified by their unique user IDs rather than their names. This allows the system to let users easily change their name while their permissions (which are stored in different tables) remain. That means, instead of changing the admin’s password, I could set his user ID temporary to an ID that doesn’t exist (how about 0?), and set my own ID to 1.

There’s an important difference between those two methods, though. As soon as we set the admin’s ID to 0, he won’t have any powers anymore. And besides, he won’t be able to log in, due to vBulletin verifying the status like this:

if ($vbulletin->userinfo['userid'] == 0) { /* not logged in */ }

0 is the default user ID for “guests”, by the way.

The other difference is, that we cannot give our own account admin rights, because it already has them! But only as long as we don’t change the ID back to the old one, which we should do quickly, before anyone notices. So if we want our very own account, with permanent rights, we have to create a new one (this can be done easily through the admin panel). If you want to give your existing account new rights, the other method should be applied. (Or… register a new account before changing IDs, and work from there.)

Moving on… The user IDs start with 1, and automatically increment by one with each user that registers. The easiest way of not messing up IDs is probably setting the admin’s ID to 0. It’s considered an integer (INT), meaning the database field will be able to hold it, and more importantly, it won’t conflict later with other IDs.

We can change the IDs by running a simple query similar to:

UPDATE `user` SET `userid` = 0 WHERE `userid` = 1

And then we set our own ID to 1, running almost the same query.

UPDATE `user` SET `userid` = 1 WHERE `userid` = XXXX

Obviously, XXXX needs to be replaced with our own user ID. I didn’t dig too deep into vBulletin’s code, but we might need to re-authenticate after making the changes. But you’ll see…

That’s most of the secret! Do whatever you need to do with the powers, and change the IDs back whenever needed, using the same queries as above, only swapping IDs.