Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Making your Drupal web server secure, step by step

Parent Feed: 

In the previous article, we covered How to stay out of SPAM folder? and today we will learn how to secure our Drupal web server.

Setting up Firewall

So, we have Debian OS powering our Drupal web server, and we need to make it secure, adjust everything so as to minimize all risks. First of, we want to configure the firewall. Basic stuff. Our "weapon of choice" here is IPTables.

Initially, the firewall is open, all traffic passes through it unimpeded. We check the list of IPTables rules with the following command:

# iptables -L -v -n

Chain INPUT (policy ACCEPT 5851 packets, 7522K bytes)
pkts bytes target prot opt in out source destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination

Chain OUTPUT (policy ACCEPT 320M packets, 19G bytes)
pkts bytes target prot opt in out source destination

All clear. To remove all IPTables rules, we use the following command:

iptables -F

Default IPTables rules

Default rules are useful and convenient. In IPTables, they are set with the help of policies (-P). It is a common practice to drop all packets and have a series of permission rules for specific cases.

The following rule allows dropping all packets:

iptables -P INPUT DROP

Additionally, you can up the security by outlawing forwarded packets, that is, packets routed by firewall to their destination. To do that, introduce the following rule:

iptables -P FORWARD DROP

For loopback, we allow local traffic:

iptables -A INPUT -i lo -j ACCEPT

The following rule makes use of the state (-m) module. It allows checking the state of the connection, which can be RELATED or ESTABLISHED. The connection is made only when it meets the rule. ESTABLISHED means there were packets already sent through the connection, and RELATED indicates it is a new connection made by forwarding a packet, but this new connection is associated with an existing connection.

The following rule allows operation of all previously initiated connections (ESTABLISHED) and connections related to them:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

And this rule allows new connections too:

iptables -A OUTPUT -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

This rule below allows forwarding new, established and related connections:

iptables -A FORWARD -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

The following couple of rules says all packets that cannot be identified (and given a status) should be dropped.

iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

iptables -A FORWARD -m conntrack --ctstate INVALID -j DROP

Allowing what needs to be allowed

The next step we make implies setting up rules that allow this and that and thus ensure correct operation of our web server. We arrange those rules in a separate chain.

First, we create custom chains:

iptables -N my_packets

The following command makes the switch to a user-defined chain:

iptables -A INPUT -p tcp -j my_packets

There is a number of restrictions imposed on going through the chains

  1. the chain must be created before it is switched to;

  2. the chain must be in the same table as the chain from which the switch is made.

Next, we open the port for SSH. Important: be sure to specify your port if it was changed!

iptables -A my_packets -p tcp -m tcp --dport 22 -j ACCEPT

If SSH is only available to a number of persons using static IPs, it makes sense to set up IP-based restrictions. To do this, we run the following command instead of the previous one:

iptables -A my_packets -s х.х.х.х -p tcp -m tcp --dport 22 -j ACCEPT

with х.х.х.х being the IP from which the connection is made.

Since we have a web server running, we need to allow firewall listening on ports 80 and 443. We do this with the following commands:

iptables -A my_packets -p tcp -m tcp --dport 80 -j ACCEPT

iptables -A my_packets -p tcp -m tcp --dport 443 -j ACCEPT

To complete all these rules, we can set up a some more for specific cases.

Remote connections to MySQL server

If such is possible, it is better to have the connections restricted by IP:

iptables -A my_packets -s х.х.х.х -p tcp -m tcp --dport 3306 -j ACCEPT

Server receives mail

The following set of rules helps in such a case:

iptables -A my_packets -p tcp -m tcp --dport 110 -j ACCEPT

iptables -A my_packets -p tcp -m tcp --dport 143 -j ACCEPT

iptables -A my_packets -p tcp -m tcp --dport 993 -j ACCEPT

iptables -A my_packets -p tcp -m tcp --dport 995 -j ACCEPT

That is all. These rules are sufficient for our web server to work correctly. Other ports and protocols follow the REJECT rule we set up for them, i.e. they do not accept anything:

iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable

iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset

iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable

Compared to DROP, REJECT implies sending the "Port unreachable" ICMP message to the sender. --reject-with option allows changing type of ICMP message; its args are as follows:

  • icmp-net-unreachable — network unreachable;

  • icmp-host-unreachable — host unreachable;

  • icmp-port-unreachable — port unreachable;

  • icmp-proto-unreachable — protocol unreachable;

  • icmp-net-prohibited — network prohibited;

  • icmp-host-prohibited — host prohibited.

    By default, the message has the port-unreachable arg.

You can reject TCP packets with tcp-reset arg, which implies sending an RST message. In terms of security, this is the best way. TCP RST packets are used to close TCP connections.

Setting up fail2ban

Fail2ban is a simple local service that keeps track of log files of running programs. Guided by the rules, the service blocks IPs from which attacks come.

Fail2ban successfully protects all popular *NIX setups (Apache, Nginx, ProFTPD, vsftpd, Exim, Postfix, named, etc.), but its main advantage is the SSH server brute-force protection enabled right after you launch it.

Installing fail2ban

Fail2ban is listed in the repository, so it is very easy to install it:

apt-get install fail2ban

That's it, your SSH is now protected from brute-force attacks.

Setting up fail2ban

First of all, we need to configure SSH protocol protection. It takes finding the [ssh] section in jail.conf file and ensuring the enabled parameter is set to true.

Next, we set up monitoring with fail2ban:

  • filter - used filter used. The default is /etc/fail2ban/filter.d/sshd.conf;

  • action - actions performed by fail2ban when detecting an ip the attack comes from; the response rules are listed in /etc/fail2ban/action.d., so the value of this parameter cannot be something that is not there;

  • logpath - full path to the file storing data on attempts to access VPS;

  • findtime - time (in seconds) the suspicious activity lasted;

  • maxretry - maximum allowed number of attempts to connect to the server;

  • bantime - the time the blacklisted ip is banned for.

Important: it is not necessary to provide values for all settings, if you skip anything, the main [DEFAULT] settings (found in the namesake section) will be applied. The most important thing here is to make sure the setting's enabled, i.e. the value for enabled is true.

Making SSH protocol secure

Let's look into details of response settings. Below is an example of fail2ban configuration on the SSH port:

[ssh]
enabled = true
port     = ssh
filter = sshd
action = iptables[name=sshd, port=ssh, protocol=tcp]
    sendmail-whois[name=ssh, dest=****@yandex.ru, sender=fail2ban@***.ru]
logpath = /var/log/auth.log
maxretry = 3
bantime = 600

All these lines mean the following: If there were more than 3 failed attempts to connect to the server through the main SSH ports, the ip used for authorization is blocked for 10 minutes. The ban rule is added to IPTables, and the server owner receives a notification to the address specified in the dest variable. In the notification contains the blocked ip, WHOIS-data about this ip and the ban reason.

An extra measure to protect SSH implies activating the following section:

[ssh-ddos]
enabled = true
port     = ssh
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 2

Configuring site files access permissions

A server cannot be considered safe if files access permissions were not configured. The following example is one of the ways to change owner and permissions on files/directories of a Drupal-powered site. Here:

Permissions setup routine:

#cd /path_to_drupal_installation
#chown -R webmaster:www-data .
#find . -type d -exec chmod u=rwx,g=rx,o= '{}' \;
#find . -type f -exec chmod u=rw,g=r,o= '{}' \;

www-data user must have write permissions for the directory, so the "files" directory in the sites/default (and any other site directories if we have a multi-site setup) has different permissions. The owner group's s bit applies to the directory, too, since we need all files created there by the web server to have the identifier of the webmaster directory group and not that of the group of owner that created the file in this directory.

#cd /path_to_drupal_installation/sites

#find . -type d -name files -exec chown -R www-data:webmaster '{}' \;
#find . -type d -name files -exec chmod ug=rwx,o=,g+s '{}' \;
#for d in ./*/files
do
  find $d -type d -exec chmod ug=rwx,o=,g+s '{}' \;
  find $d -type f -exec chmod ug=rw,o= '{}' \;
done

Temp directory here also takes on different permissions since we need web server writing into this directory.

#cd /path_to_drupal_installation
#chown -R www-data:webmaster tmp
#chmod ug=rwx,o=,g+s tmp

settings.php and .htaccess: special permissions

settings.php file contains database password and user name, and they are plain text there. To avoid problems, we want to allow just some users read it and nothing else. Typically, it means banning "other" out:

#chmod 440 './sites/*/settings.php'
#chmod 440 './sites/*/default.settings.php'

.htaccess is the Apache configuration file, which gives power over operation of the web server and site settings. Accordingly, only a handful of users should have permission to read the file's contents:

#chmod 440 ./.htaccess
#chmod 440 ./tmp/.htaccess
#chmod 440 ./sites/*/files/.htaccess

Secure configuration of the web server

To make the system as secure as possible, we want to make some changes to the SSH server settings. It is best to run it on a non-standard port, otherwise it will be constantly attacked by bruteforcing bots picking passwords. Same as other Linux distributions, Debian has SSH on port 22. We can change it to, say, 2223. In addition, it would be wise to change the setup so as to allow root's connection with ssh key only. By default, in Debian, root cannot be authorized with only a password over SSH.

It's better to change SSH port before configuring the firewall. If you forgot that, you need to do the following:

  1. Add the rule allowing connection on a new port to IPTables before changing it:

iptables -A my_packets -p tcp -m tcp --dport 2223 -j ACCEPT
  1. Change the SSH server port in # nano /etc/ssh/sshd_config. Find the relevant lines and make them look like this:

Port 2223
PermitRootLogin prohibit-password

PubkeyAuthentication yes
ChallengeResponseAuthentication no

Do not forget to save the changes! Then restart the SSH server:

# service sshd restart

Now, we want to check the changes:

# netstat -tulnp | grep ssh

tcp 0 0 0.0.0.0:2223 0.0.0.0:* LISTEN 640/sshd
tcp6 0 0 :::2223 :::* LISTEN 640/sshd

Everything is fine. The SSH server listens on port 2223, from now on, new connections will go through this port only, and after restarting SSH the old connection will not be dropped.

Attention! Before disabling password authorization for the root user, make sure you have your public key in the /root/.ssh/authorized_keys file. Also, it is wise to have another account to connect to the server with a password.

Author: 
Original Post: