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
-
the chain must be created before it is switched to;
-
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:
-
webmaster belongs to the webmaster group and is the owner of the site;
-
the site itself resides on a server where the web server's user is www-data.
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:
-
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
-
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.