19th February 2010
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. But you still shouldn’t try this. 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. vBulletin has a pretty solid code base, which is not easily hacked. But 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 moniker).
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 on the client-side only (big big mistake). I opened up Firebug and removed this attribute from the text field: maxlength=”21”… and the limit was gone. If you absolutely must limit the input length, don’t trust the user. Do it on the server-side as well. Input lengths wouldn’t even be an issue if proper escaping would have been applied.
So how can I use this exploit now? I can write to the “users” table only, that means I could for example 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 all 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. But 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, and we all still lived in a perfect world.
I only have “UPDATE” access to the “users” table, so I need a field somewhere 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. And yes, I do own a license.)
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.
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 or crack. But I need it so I can put it back later when I’m done, so he won’t notice I temporarily “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 unique MD5 hashes, to avoid collisions, and so no one can find the original password using rainbow tables. But in this case, it doesn’t secure anything at all. We can just grab this salt again and create a new password with it. We don’t even need to know what its actual value is.
We can use native SQL to grab the salt and create a new password:
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:
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 going be too late anyway! ;)
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 pretty much anything else I can think of. I have complete control, and all because of a silly, seemingly innocuous value in a tiny SQL query that no one cared to escape.