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 message board running on vBulletin 3.8 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 single detail, as I don’t actually want anyone on this site to try this out. 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 myself 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, or “mods”, 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 the “maxlength” attribute from the text field, 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 “user” 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 “user” 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 after giving myself all necessary admin rights, as if nothing ever happened and we all still lived in a perfect world.
I only have “UPDATE” access to the “user” table, so I need a field somewhere in there that can hold at least 32 characters for the MD5 hash. 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 even want to brute force or crack. I just need it so I can put it back later when I’m done, so he won’t notice I temporarily “borrowed” his account.
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 personal salt, mainly so that multiple users sharing the same password don’t end up with the same hash, and at the same time rendering rainbow tables useless. But in this case, it doesn’t secure anything at all. We can just grab the victim’s salt 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 create a new password using the current salt:
Easy! Now that I have set my own password, I can just log into his account, using his original username and the new password I just created, and give myself all admin rights that tickle my fancy. After doing so, I can restore his old password by running a simple SQL query:
This might be obvious, but you can get the hash by going to your profile and reading the value from the MSN field.
And that’s pretty much it! 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.