Some four years ago, back at my first workplace, one of our colleagues hailed the rest of us:

Hey guys, a friend of mine released a commercial CMS, he's already got a few clients and he asked if we'd like to poke around and see if we can break anything.

Being a prominent enthusiast of slacking off at work, I happily stopped whatever I was doing at the moment and asked him to provide me the URL to said CMS so I could take a look.

The task was made easier by the fact that, at the time, the CMS had a public demo instance where I could navigate through the system and check how various things work – not only visitor-side, but also admin-side. Speaking of admin-side, the first vulnerability became apparent almost immediately:

Vulnerability Ⅰ: No user verification in PHP code

The admin panel, as it often happens, was accessible by navigating to /admin/ .

The crucial thing is that the panel was protected solely via means of HTTP Basic Auth – there was no user verification of any kind in the PHP code. From a cracking perspective, this was really good news:

If I found a way to bypass the HTTP Basic Auth mechanism and got to the admin panel some other way, I'd instantly get admin access, no questions asked.

Since the PHP code had no concept of an "active user", this meant that there weren't any kind of user roles nor privilege separation – if you had access to the admin panel, you could do whatever you want.

Vulnerability Ⅱ: Path traversal

My next step was crystal clear: find a way to get unauthorized access to the admin panel. I decided to start with taking a look at the CMS's routing mechanism. All the pages' URLs followed the /index.php?p=SUBPAGE schema.

Hmmm... How about editing the p parameter? What if I put ?p=blablabla in the URL?

Wonderful - I got a PHP error saying that blablabla.php was not found, which meant that the value of the p parameter was simply passed to include to load another PHP file. If there was no "file exists" check, then maybe the value wasn't actually validated in any way. So how about putting admin/index in the parameter?

At this moment, half of me went "Bingo!", whereas the other half went "You can't be serious.". As you can see, by putting admin/index as the value of the p parameter, I got access to the admin panel.

Gaining arbitrary code execution

At this point I could call it a day – I mean, I found a way to get unauthorized admin access – but I decided to see and try if I can get the system to perform arbitrary code execution. Since the p parameter seemed to just happily include whatever .php file it was pointed at, could I upload a PHP script and execute it? Only way to find out was to navigate to the file upload panel see if it works.

The file upload panel is a bit broken. First, there's no upload button – this can be fixed by just copy-paste'ing the upload form from the, uh, "authorized" version of the admin panel. Second, there should be a list of recently uploaded files underneath – but looking at the "authorized" page, I could see that the list of files is returned via AJAX, so I could just make a direct request to the AJAX resource.

As you can see, my file got uploaded and is present on the list. Now it's just a matter of editing the p parameter one final time...

At this moment I decided that, well, there isn't really any step up from gaining code execution (okay, I could try to gain root access, I guess), so it was time to end my little game. I passed the information about the vulnerabilities to my friend, who in turned passed it to the CMS author. The bugs were fixed some time after that.

Wrapping up

To be perfectly honest, I was a bit disappointed with how easy it turned out. The vulnerabilities exploited during this hack were some of the most basic stuff you would check for when attacking a website. Frankly, I felt kinda embarassed that someone was selling – charging money – for a product which didn't follow what I personally consider a basic security checklist. The abundance of PHP warnings also hinted at a very happy-go-lucky approach to programming, where you only take care of the happy path and if anything breaks along the way, oh well, who cares? (Hint: definitely not you.)

If there's any security advice to be taken out of this, let's call it, case study – that'd be mainly this one thing:

Never trust user input. Validate everything you received from the user. Think of any malicious ways someone could use the input to do something the system wasn't meant to do.

That's pretty much the main thing that made this exploit possible. But let me take this moment to also suggest a few other things:

Implement privilege separation. Well, it's hard to talk about this when the CMS had no concept of a user at all, but either way – a system where everyone is an admin is easier to break into than one where each user has a different access level (or better yet, a different set of role-based permissions). Of course, if you can bypass authentication like in this CMS, privilege separation won't help you much, but at least you limit the amount of damage that can be done using a compromised account. If users can upload files through your service, and they're not limited in the kind of files they can upload – make sure the upload directory is configured to only send raw data and that it's not possible to trigger a server-side execution of uploaded content. Again – in this CMS, I've used PHP's include mechanism and path traversal to execute my script, so this advice wouldn't change much... but if you think about compromised accounts, or rogue users, this step is essential.

Also, don't rely on some random blog on the internet to give you security advice and just go read the OWASP Top 10 list, if you haven't already.

References