Notes:
- Change, me, myhost, myip etc. to your username, hostname, ip address and so on.
- Run all commands as root unless otherwise directed.
- You might want to look at mounting /var and /home on separate partitions.
- I've just allowed all members of the wheel group to operate as root. This is the height of laziness and highlights the fact that I'm just a developer that's stolen a sysadmin's woolly hat. It works fine, but the phrase "can do better" springs to mind.
echo "HOSTNAME=myhost" >> /etc/sysconfig/network
hostname "myhost"
echo "myip myFQDN myhost" >> /etc/hosts
Note: add a reverse dns entry too if you plan to run a mail server.
ln -sf /usr/share/zoneinfo/UTC /etc/localtime
vi /etc/sysconfig/clock
You may want to choose GB to enable daylight saving time.
useradd -m -G wheel me
passwd me
vim /etc/sudoers
Uncomment the line that allow users in the wheel group go mad:
## Allows people in group wheel to run all commands
%wheel ALL=(ALL) ALL
Logout, and then try to login again, but as the new user:
ssh me@myip
sudo -i
exit
Assuming it all works, be kind to your fingers and add your pubkey to authorized keys:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "your public key here" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Be even kinder to yourself, and add something to the ssh config on your own machine:
vim ~/.ssh/config
...
Host myhost
HostName myip
User me
vim /etc/ssh/sshd_config
...
PermitRootLogin no
LoginGraceTime 30
MaxAuthTries 3
MaxStartups 3:50:10
Restart the ssh daemon after making changes:
service sshd restart
Note RE running sshd on a non-standard port - you can do this, and I normally do this, but as we'll be using fail2ban or denyhosts to prevent brute-force attacks, doing so is really just going to help you reduce the number of alerts RE brute-force attacks and keep your logs clean, definitely worth doing, but not strictly necessary.
Run the following commands as root to create a basic set of firewall rules that will allow traffic on standard ports for http, https, smtp ftp etc., block anything else, and drop portscans. Note: I think I originally just took these from the following linode wiki page, which might be worth a look as it has some nice comments explaining why the rules are added: http://www.linode.com/wiki/index.php/CentOS_IPTables_sh.
First, flush any existing rules (shouldn't be any, but just in case):
iptables -F
Then create the rules:
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -s 10.0.0.0/255.0.0.0 -j DROP
iptables -A INPUT -s 169.254.0.0/255.255.0.0 -j DROP
iptables -A INPUT -s 172.16.0.0/255.240.0.0 -j DROP
iptables -A INPUT -s 127.0.0.0/255.0.0.0 -j DROP
iptables -A INPUT -s 224.0.0.0/240.0.0.0 -j DROP
iptables -A INPUT -d 224.0.0.0/240.0.0.0 -j DROP
iptables -A INPUT -s 240.0.0.0/248.0.0.0 -j DROP
iptables -A INPUT -d 240.0.0.0/248.0.0.0 -j DROP
iptables -A INPUT -s 0.0.0.0/255.0.0.0 -j DROP
iptables -A INPUT -d 0.0.0.0/255.0.0.0 -j DROP
iptables -A INPUT -d 239.255.255.0/255.255.255.0 -j DROP
iptables -A INPUT -d 255.255.255.255 -j DROP
iptables -A INPUT -p icmp -m icmp --icmp-type 17 -j DROP
iptables -A INPUT -p icmp -m icmp --icmp-type 13 -j DROP
iptables -A INPUT -p icmp -m icmp --icmp-type any -m limit --limit 1/sec -j ACCEPT
iptables -A INPUT -m state --state INVALID -j DROP
iptables -A INPUT -p tcp -m tcp --tcp-flags RST RST -m limit --limit 2/sec --limit-burst 2 -j ACCEPT
iptables -A INPUT -m recent --rcheck --seconds 86400 --name portscan --rsource -j DROP
iptables -A INPUT -m recent --remove --name portscan --rsource
iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j LOG --log-prefix "Portscan:"
iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j DROP
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable
iptables -A FORWARD -m state --state INVALID -j DROP
iptables -A FORWARD -m recent --rcheck --seconds 86400 --name portscan --rsource -j DROP
iptables -A FORWARD -m recent --remove --name portscan --rsource
iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j LOG --log-prefix "Portscan:"
iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j DROP
iptables -A FORWARD -j REJECT --reject-with icmp-port-unreachable
iptables -A OUTPUT -m state --state INVALID -j DROP
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 21 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 43 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 67 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 110 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 123 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 143 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 587 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 993 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 995 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 9418 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
iptables -A OUTPUT -j REJECT --reject-with icmp-port-unreachable
Now, check it worked:
iptables -L
And open another terminal and try to ssh to the server.
Save the configuration, so it works after a restart of iptables:
service iptables save
And sanity check the saved file (can't be too cautious):
cat /etc/sysconfig/iptables
Restart iptables and again check that the rules have been applied:
service iptables restart
iptables -L
Note: you may see a failure message when you restart iptables, referring to a "security" table which doesn't exist. This is fine for our purposes, you can ignore it. It's a known issue caused by a security table being defined in the latest linux kernel, which iptables doesn't know about. Google to read more if you're interested.
Finally, make sure iptables starts on boot:
chkconfig iptables on
yum update
Note: this is far from exhaustive and just addresses a handful of well-documented issues. Don't assume this means your apache web server is "secure", even if you follow the optional steps to install and configure mod_security. You want "secure", hire an expert with indemnity insurance.
Disable TRACE and OPTIONS HTTP methods by adding the following to the end of the httpd.conf file:
vim /etc/httpd/conf/httpd.conf
...
TraceEnable off
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_METHOD} ^OPTIONS
RewriteRule .* - [F]
</IfModule>
Disable mod_status, mod_info, all proxy modules, all dav modules, all ldap modules and anything else you know you don't need by simply commenting them out.
Restrict the information apache provides with http responses by setting the ServerTokens directive to Product.
ServerTokens Prod
Deny access to files outside /var/www by default. Update the existing Directory directive for the root path, and add a new one for /var/www:
<Directory />
Order deny,allow
Deny from all
Options None
AllowOverride None
</Directory>
<Directory /var/www>
Order allow,deny
Allow from all
Options FollowSymLinks
AllowOverride None
</Directory>
Finally, disable SSLv2 and weak encryption ciphers:
vim /etc/httpd/conf.d/ssl.conf
...
SSLProtocol -ALL +SSLv3 +TLSv1
SSLCipherSuite ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM
Check the configuration, then start apache and configure it to start on boot:
apachectl configtest
service httpd start
chkconfig httpd on
Ok, so mod_security can cause problems when it rejects things that look suspicious but are perfectly normal within the realm of your web app. However, it really isn't that difficult to tweak the configuration and get things working if you do run into trouble, so you should at least give it a go. Install it via yum:
yum install mod_security
Reload the apache configuration and it should be working:
service httpd reload
Check if you need to fix a bug in the core rule set (I did, with mod_security v2.5.12). See http://comments.gmane.org/gmane.comp.apache.mod-security.user/7262 for the modification that needs to be made to file /etc/httpd/modsecurity.d/base_rules/modsecurity_crs_30_http_policy.conf
Then, check if you need to fix another bug, this time with the default data directory for mod_security. The default is the the apache log directory, which mod_security may not have permisson to write to (check /var/log/httpd/error_log for confirmation).
vim /etc/httpd/conf.d/mod_security.conf
...
SecDataDir /tmp
See https://bugzilla.redhat.com/show_bug.cgi?id=732450 and https://bugzilla.redhat.com/show_bug.cgi?id=569360 for more info.
For rails applications, I found myself removing a few rules that were causing problems, but which I also knew weren't necessary because of the built in XSS and CSRF protection in rails, and because I set PassengerFriendlyErrorPages off. The simplest solution was just to use the DirectoryMatch directive:
vim /etc/httpd/conf/httpd.conf
...
<DirectoryMatch /var/www/[^/]+/current/public>
# Reomve some mod_security rules that are causing problems for
# rails apps, and are known to be unncessary for rails apps.
SecRuleRemoveById 90008 # self-executing JavaScript functions
SecRuleRemoveById 900030 # common XSS concatenation patterns
SecRuleRemoveById 970901 # response status 5xx
</DirectoryMatch>
If you find yourself running into seemingly unsurmountable problems, you can use the SecRuleEngine directive to disable the rule engine per virtual host. Just update your vhost config and add "SecRuleEngine Off". You could alternatively disable the rule engine by default by editing /etc/httpd/conf.d/mod_security.conf
You will need to add the EPEL repo to install fail2ban or denyhosts with yum
rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-5.noarch.rpm
yum install fail2ban
Whitelist at least one remote IP address which will never be banned:
vim /etc/fail2ban/jail.conf
...
ignoreip = 127.0.0.1 officeip homeip etc
Start the daemon
service fail2ban start
And configure it to start on boot
chkconfig fail2ban on
yum install denyhosts
Whitelist at least one remote IP address which will never be banned:
vim /var/lib/denyhosts/allowed-hosts
...
# We mustn't block localhost
127.0.0.1
# my office
officeip
# my house
homeip
Start the daemon
service denyhosts start
And configure it to start on boot
chkconfig denyhosts on
Test that it works by then creating a number of failed ssh logon attempts. After 5 or so logon attempts (depends on syslog buffering), the server should start to reject tcp connections and the ssh client should appear to hang while it waits to report a connection timeout.
ssh foo@myip <- just keep repeating this until the connection is rejected
Edit /etc/sysctl.conf and make sure the following parameters are set. If the entries don't exist, add them:
vim /etc/sysctl.conf
...
# Reboot after a kernel panic
kernel.panic = 10
# Enable TCP SYN Cookie Protection
net.ipv4.tcp_syncookies = 1
# Disable IP Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Disable ICMP Redirect Acceptance
net.ipv4.conf.all.accept_redirects = 0
# Enable IP Spoofing Protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Don't allow outsiders to alter the routing tables
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
# Don't pass traffic between networks or act as a router
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Ignore ICMP Requests
net.ipv4.icmp_echo_ignore_all = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Enable Bad Error Message Protection
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Disable IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
Most guides to disabling IPv6 tell you to disable the module. Don't do this. Disabling the module can cause problems for other things that depend on it. You've already disabled IPv6 in the kernel, that's sufficient. You can test this for yourself by trying ping6 localhost.
With IPv6 disabled in the kernel, you should update your network configuration to ignore it:
vim /etc/sysconfig/network
...
NETWORKING_IPV6=no
and confirm that the ip6tables daemon is not running and won't run on boot:
service ip6tables stop
chkconfig ip6tables off
If you plan to run postfix to send mail, you'll need to update the config file and tell it not to bother with IPv6:
vim /etc/postfix/main.cf
...
# Enable IPv4, and IPv6 if supported
inet_protocols = ipv4
- http://www.linode.com/wiki/index.php/CentOS
- http://www.puschitz.com/SecuringLinux.shtml
- http://seerofsouls.com/wiki/How-Tos/Fail2Ban
- http://centoshelp.org/security/fail2ban/
- http://centoshelp.org/security/securing-sshd/
- http://centoshelp.org/security/denyhosts/
- http://www.linode.com/wiki/index.php/CentOS_IPTables_sh
- http://www.cyberciti.biz/faq/tcp-wrappers-hosts-allow-deny-tutorial/
- http://people.redhat.com/sgrubb/files/hardening-rhel5.pdf
- http://wiki.centos.org/FAQ/CentOS6
- https://bugzilla.redhat.com/show_bug.cgi?id=641836#c17