Your PHP server could be Super Hackable

Published:

What if I told you that anyone with access to the WordPress admin panel on your site has effective access to your whole server—or at least the reaches of your server that you’ve configured to be nginx/apache-administrable?

What if I told you that WordPress admin panel access is, on most servers, functionally indistinguishable from full database read/write access?

What if I told you that through the WordPress admin panel, an administrator could see what other sites you’re running on a given host? That they could zip up and copy those sites? Or, worse, that they could delete them permanently?

I’m going to tell you how.

Setup

So you administer a couple of servers. You’ve got a few running at DigitalOcean, maybe a few running through Laravel Forge or some other administrative interface. You know how to set up Nginx or Apache with PHP-FPM. You have opinions about whether or not to use MariaDB instead of MySQL. You’re a sysadmin.

Maybe you’ve got a few sites running on a single server. Which makes sense—even the cheapest VPS at most hosting companies will be able to handle a couple hundred connections per minute. It just doesn’t make sense to host a site that only get 20 hits a day on its own server. It helps keep costs down without any noticeable drop in performance. It’s just fiscal prudence.

At the same time, it also means disparate admins are logging into your server all the time. Which means that even if they just have standard WordPress credentials, they all have read/write access to, at the very least, their WordPress instance.

Keep in mind this doesn’t just go for WordPress. The following applies to just about any content management system (CMS) with write access to the filesystem—e.g. for uploading images, or creating templates.

While this means that your users can upload and edit their photos with ease, it also means that they can download ACE IDE or WP File Manager and start messing with the filesystem: editing templates, adding functions, extending functionality. Still, this isn’t a huge cause for concern, as these plugins generally limit access to the WordPress instance they’re installed in. In most cases, the worst that’s going to happen is someone’s going to forget a semicolon and the site is going to crash.

In some cases, though, clever WordPress admins will take advantage of the PHP sandbox you’ve given them and start poking around.

Enter shell_exec & co.

The function shell_exec (and its dimwitted sibling exec) are probably the most dangerous functions in a suite of command-line timebombs provided by a standard PHP install. These allow you to execute arbitrary code on the command line with whatever permissions you’ve given to your server.

That means that if you’ve got a single server running four or five sites, shell_exec is going to give you permission to mess with all of them.

In action

No way around it here—you’re going to have to be familiar with the command line to be even a little bit dangerous here. Shell permissions don’t mean much if you can’t write shell code. Which, sorry.

Say you want to see where the current file lives:

echo nl2br(shell_exec('pwd'));

nl2br is a function that turns newlines into <br> tags so formatting is maintained when you output to HTML.

That might give you something like /home/$user/example.com, which leads you to believe that there might be other sites lurking in the /home/$user directory. You can check out that one directory up with a simple ls:

What gets displayed here basically tells you as much as you need to know about how much damage you can do on the server. I’m not going to spell it all out because someone could copy what I’ve written here and paste it into someone’s functions.php and cause a whole lot of havoc without realising quite what they’re doing.

But if you’ve come this far, the mess that you could make should be pretty clear.

Mitigation time

This would be a pretty poor excuse for a vulnerability report if it didn’t include some way to prevent it. Fortunately, the solution is pretty simple.

PHP provides a disable_functions directive for your php.ini, taking a comma-separated list of functions for PHP to refuse to execute. You’ll need to find your php.ini first, which you can do with the following command:

$ php -i | grep ini

Did you just paste shell code from the Internet into your server?

Anyway, assuming that you’ve figured out where your php.ini is, open it up and find the line that starts with disable_functions. Set the whole line equal to:

disable_functions=exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

This disables not just shell_exec and exec, but a handful of other functions that can communicate with other servers and mess with your config. I got this list from here as it’s a pretty good balance between security and practicality, but you can amend the list as you see fit.

From here it’s just a question of restarting PHP, which will consist of a command something like sudo systemctl phpX.X-fpm restart depending on your OS and the version of PHP that you’re running.

Which, God help you if it’s less than 7.