Contents
Computer security is once again becoming a hot topic for administrators. There are dozens of new sites springing up around the web, and each is slinging their own ‘Perfect’ setup instructions. They have the usual bell curve of good advice, okay advice, and advice that will effectively leave you with a smoldering pile of rubble where your data used to be. Here, we're going to discuss locking down a CentOS 5 system the proper way. This proper way is based on the NSA RHEL5 guide, Steve Grubb's RHEL Hardening presentation, and other reputable sources.
For the purposes of this wiki article, we are assuming that we are configuring a server. Laptops and workstations may have different requirements, such as encrypted filesystems which are not covered here. Likewise, they may require the use of USB storage or wireless modules, which are disabled in this security guide.
File System Partitioning
By separating file systems into various partitions, you can fine tune permissions and functionality. Doing so will provide you greater granularity for permissions, as well as adding a layer of security for any potential bad guys to work through.
Steve Grubb suggests, and quite rightly so, that areas where users have write privileges be kept on their own partition. This allows you to prevent hard link privilege escalation attempts, prevent creative device additions, and other unsavory behavior.
1. Modifying fstab
Once you have your partitions broken out and sized accordingly, you can begin to restrict the various mount points as much as possible. You should add nodev, noexec, and nosuid wherever possible. An example of a decently restricted /etc/fstab file is below:
/dev/VG_OS/lv_root / ext3 defaults 1 1 /dev/VG_OS/lv_tmp /tmp ext3 defaults,nosuid,noexec,nodev 1 2 /dev/VG_OS/lv_vartmp /var/tmp ext3 defaults,nosuid,noexec,nodev 1 2 /dev/data_vol/lv_home /home ext3 defaults,nosuid,nodev 1 2 /dev/VG_OS/lv_var /var ext3 defaults,nosuid 1 2 /dev/data_vol/lv_web /var/www ext3 defaults,nosuid,nodev 1 2 /dev/sda1 /boot ext3 defaults,nosuid,noexec,nodev 1 2 tmpfs /dev/shm tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0 /dev/_VG_OS/lv_swap swap swap defaults 0 0
Obviously you'll need to modify this example to suit your own system. LVM, volume names, labels etc are all subject to change. Please don't copy this example verbatim and expect it to work for you.
The webserver mount can also be set noexec, however this will impact cgi based applications, as well as server side includes which rely on the execute bit hack. If you're not using cgi applications, I would recommend at least testing noexec and using it if there are no negative side-effects.
Package installs
When it comes to the packages you install on your systems, it's good to remember that less is more. More things on the system mean more things to track for vulnerabilities and updates, as well as more things which can potentially get in the way of something you're trying to do. Since server tasks are often very different, I won't even attempt to give a list of what should or should not be installed. Instead, I recommend a basic strategy of customizing the package list and unchecking everything but base. Once you have this done, generate a list of what's currently installed, and further prune back what you don't need.
yum list installed >> ~/installed.txt
|
For x86_64 users: If you don't need i386/i686 packages for compatability purposes, you may want to remove them as well, by using yum remove *.i?86, and then keep them gone by adding exclude = *.i?86 to your /etc/yum.conf |
1. Regular Updates
Now that we have our minimal package set in place, we have to update them periodically. I do not recommend the use of yum-updatesd. I've had far too many bad experiences with it hogging resources for me to advise its use. You could set a cron job to update, or check for updates, which is what the NSA guide recommends. You could simply manually apply the updates on a weekly basis, or you could go for broke and set up a spacewalk server to push and manage updates across multiple systems. However you decide to manage your update policy, it basically boils down to this. Apply updates in a timely fashion to avoid problems. Additionally, subscribing to the CentOS-Announce mailing list is always a good idea. This way you will be sent a notification every time there is an update, and you can apply any critical patches early if need be.
Once you have your package list sufficiently pruned and updated, you will want to go through your services list and disable anything you won't be using on your server. Again, since every environment is different, I won't pretend to tell you what you should or should not turn off; however, you do need to ask yourself if that server REALLY needs bluetooth running :-P
Basic Hardening
Now that we have the partitioning done, restrictions added, and package list pruned, it's time to get around to the meat of the matter. It's time to lock down the system. Some of these may or may not apply based on your environment. You should consider all of them, but your particular situation may dictate doing things a different way.
1. Physical Protection
- The only folks allowed near the server should be directly responsible for it.
- Don't allow the system to boot from removable media as the default option
- Require a bios password to change boot options. OS security doesn't matter much if your attacker brings their own OS to the party.
- Use a password for grub. All the security in the world is for naught if someone can simply pass some arguments to your loader and disable your security.
- Require a password for single user mode. Same reason as above.
- Most servers don't need usb storage devices. Disable the usb-storage driver if possible.
For directions on protecting grub, see BIOS and Boot Loader Security. To require root's password for single user mode, you can use:
echo "# Require the root pw when booting into single user mode" >> /etc/inittab echo "~~:S:wait:/sbin/sulogin" >> /etc/inittab echo "Don't allow any nut to kill the server" perl -npe 's/ca::ctrlaltdel:\/sbin\/shutdown/#ca::ctrlaltdel:\/sbin\/shutdown/' -i /etc/inittab
Now disable USB mass storage, if you're not using it in your environment
echo "Disabling USB Mass Storage" echo "blacklist usb-storage" > /etc/modprobe.d/blacklist-usbstorage
2. User Rights
This is by far the largest category, with some of the most important stuff. Since each organization is different not all of these may apply to you, but you should consider them.
By default, users are given quite a bit of freedom. Much of this freedom can easily be stripped from them to help secure the system. Since root has the most power, we'll start with restricting root first.
2.1. Restricting Root
Once a server is up and running, root shouldn't be logging in directly except in emergency situations. These usually require hands at the console, so that's the only place root should be allowed to log in. To do this, we need to modify /etc/securetty. Additionally, no one other than root should be allowed in root's home directory. The default settings are close to this, but not quite paranoid enough.
echo "tty1" > /etc/securetty chmod 700 /root
Since we have effectively removed root's ability to log in from anywhere but the local console, it becomes necessary to use su and sudo. This offers a few secondary benefits in a multi-admin environment.
- sudo allows for granular control over privileged actions. This way a website administrator can start, stop and otherwise manage the web server without being able to affect other services.
- You get a much clearer picture of who did what in your logs, since who became root at what time is no longer a mystery.
2.2. Password Policies
Now that we have root mostly restricted, it's time to move on to everyone else. First, we need to set down some ground rules when it comes to new accounts.
- Strong passwords should be used. A strong password should have mixed case, special characters, numbers, and be longer than 8 characters.
- Password complexity requirements should be in place to enforce strong password usage.
- Passwords should be changed reasonably regularly. Some folks argue the value of changing passwords, however the longer you have a password, the longer someone has to break it. Conversely, if you're frequently changing passwords, your users will tend to use weaker passwords in order to remember them. You should to find a happy medium that suits your organization.
- Passwords shoudn't be changed more than once a day
echo "Passwords expire every 180 days" perl -npe 's/PASS_MAX_DAYS\s+99999/PASS_MAX_DAYS 180/' -i /etc/login.defs echo "Passwords may only be changed once a day" perl -npe 's/PASS_MIN_DAYS\s+0/PASS_MIN_DAYS 1/g' -i /etc/login.defs
The command below will update your system to use sha512 instead of md5 for password protection. This alleviates a number of bureaucratic security issues regarding the security of md5 for password protection. It also keeps the people wearing tinfoil hats happy too.
authconfig --passalgo=sha512 --update
2.3. Umask restrictions
Modifying the default umask can make things interesting. A umask of 077 is recommended for security, but can tend to be a pain for users who regularly share files. Be careful if you decide to implement this, and listen to your users.
perl -npe 's/umask\s+0\d2/umask 077/g' -i /etc/bashrc perl -npe 's/umask\s+0\d2/umask 077/g' -i /etc/csh.cshrc
Now is where things get a little more tricky. If a user fails to enter the correct credentials, pam_tally2 will deny access until the unlock_time is reached. Translated, if you fail to log in properly 3 times you will have to wait certain period of time before you can try again.
2.4. Pam modifications
And now we need to update /etc/pam.d/system-auth
touch /var/log/tallylog cat << 'EOF' > /etc/pam.d/system-auth #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 500 quiet auth required pam_deny.so auth required pam_tally2.so deny=3 onerr=fail unlock_time=60 account required pam_unix.so account sufficient pam_succeed_if.so uid < 500 quiet account required pam_permit.so account required pam_tally2.so per_user password requisite pam_cracklib.so try_first_pass retry=3 minlen=9 lcredit=-2 ucredit=-2 dcredit=-2 ocredit=-2 password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok remember=10 password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so EOF
The file /var/log/tallylog is a binary log containing failed login records for pam. You can see the failed attempts by running the pam_tally2 command without any options, and unlock user accounts early by using pam_tally2 --reset -u username
2.5. Reaping idle users
Now that we've restricted the login options for the server, lets kick off all the idle folks. To do this, we're going to use a bash variable in /etc/profile. There are some reasonably trivial ways around this of course, but it's all about layering the security.
echo "Idle users will be removed after 15 minutes" echo "readonly TMOUT=900" >> /etc/profile.d/os-security.sh echo "readonly HISTFILE" >> /etc/profile.d/os-security.sh chmod +x /etc/profile.d/os-security.sh
2.6. Restricting cron and at
In some cases, administrators may want the root user or other trusted users to be able to run cronjobs or timed scripts with at. In order to lock these down, you will need to create a cron.deny and at.deny file inside /etc with the names of all blocked users. An easy way to do this is to parse /etc/passwd. The script below will do this for you.
echo "Locking down Cron" touch /etc/cron.allow chmod 600 /etc/cron.allow awk -F: '{print $1}' /etc/passwd | grep -v root > /etc/cron.deny echo "Locking down AT" touch /etc/at.allow chmod 600 /etc/at.allow awk -F: '{print $1}' /etc/passwd | grep -v root > /etc/at.deny
Network Security
Now that we have the basics for the OS protected, it's time to take a look at the basic network functionality. We're not interested in too many services here. We're simply looking at the interfaces themselves and ssh for management purposes.
1. Kernel Network Security
There are a number of methods to improving network security with just a few modifications, and some module blacklisting.
1.1. Wireless has to go
Since we're looking at server security, wireless shouldn't really be an issue. If you need a wireless network, you can skip this step, because we're about to disable all the wireless drivers. You could go through the contents of /lib/modules for your current kernel and remove all the wireless drivers. This will certainly disable wireless, however it's not a permanent solution. The next time you upgrade the kernel, they'll be right back, and you'll be doing this all over again. Instead, a simple loop can be used to disable them via a blacklist file in /etc/modprobe.d
for i in $(find /lib/modules/`uname -r`/kernel/drivers/net/wireless -name "*.ko" -type f) ; do echo blacklist $i >> /etc/modprobe.d/blacklist-wireless ; done
1.2. Sysctl Security
Next we need to have a look inside /etc/sysctl.conf and make some basic changes. If these lines exist, modify them to match below. If they don't exist, simply add them in. If you have multiple network interfaces on the server, some of these may cause issues. Test these before you put them into production. If you want to know more about any of these options, install the kernel-doc package, and look in Documentation/networking/ip-sysctl.txt
net.ipv4.ip_forward = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 net.ipv4.tcp_max_syn_backlog = 1280 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.accept_source_route = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.tcp_timestamps = 0
1.3. Using TCP Wrappers
TCP wrappers can provide a quick and easy method for controlling access to applications linked to them. Examples of TCP Wrapper aware applications are sshd, and portmap. A restrictive example is below. This example blocks everything but ssh.
echo "ALL:ALL" >> /etc/hosts.deny echo "sshd:ALL" >> /etc/hosts.allow
1.4. Beefing up IPTables
The default iptables ruleset in CentOS is a little too lenient. The policy defaults are to allow traffic, there are open ports, and no real accountability for the traffic. We can do a better job.
Open up /etc/sysconfig/iptables in a text editor, and lets have a look. In the first 3 lines, there are already two problems. The INPUT and FORWARD tables are set to accept everything. Further down we see that ports, 50, 51, 5353, 631 and 22 are open. Now port 22 I don't have a problem with. The rest of them need to go, unless you want mDNS, cups, and ipsec talking to the outside world. I generally don't like strangers using my printer.
There's also no real logging of any malicious scanning or other unsavory behavior. A stronger ruleset might look like this:
#Drop anything we aren't explicitly allowing. All outbound traffic is okay *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :RH-Firewall-1-INPUT - [0:0] -A INPUT -j RH-Firewall-1-INPUT -A FORWARD -j RH-Firewall-1-INPUT -A RH-Firewall-1-INPUT -i lo -j ACCEPT -A RH-Firewall-1-INPUT -p icmp --icmp-type echo-reply -j ACCEPT -A RH-Firewall-1-INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT -A RH-Firewall-1-INPUT -p icmp --icmp-type time-exceeded -j ACCEPT # Accept Pings -A RH-Firewall-1-INPUT -p icmp --icmp-type echo-request -j ACCEPT # Log anything on eth0 claiming it's from a local or non-routable network # If you're using one of these local networks, remove it from the list below -A INPUT -i eth0 -s 10.0.0.0/8 -j LOG --log-prefix "IP DROP SPOOF A: " -A INPUT -i eth0 -s 172.16.0.0/12 -j LOG --log-prefix "IP DROP SPOOF B: " -A INPUT -i eth0 -s 192.168.0.0/16 -j LOG --log-prefix "IP DROP SPOOF C: " -A INPUT -i eth0 -s 224.0.0.0/4 -j LOG --log-prefix "IP DROP MULTICAST D: " -A INPUT -i eth0 -s 240.0.0.0/5 -j LOG --log-prefix "IP DROP SPOOF E: " -A INPUT -i eth0 -d 127.0.0.0/8 -j LOG --log-prefix "IP DROP LOOPBACK: " # Accept any established connections -A RH-Firewall-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # Accept ssh traffic. Restrict this to known ips if possible. -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT #Log and drop everything else -A RH-Firewall-1-INPUT -j LOG -A RH-Firewall-1-INPUT -j DROP COMMIT
Now arguably since we're responding to pings, dropping the traffic instead of rejecting it isn't fooling anyone. It's really personal preference. If you would rather reject the traffic, you could change the last line before COMMIT to read this way instead:
-A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited
Tamper Resistance
Since you've put a fair amount of your time into hardening the system and configuring it to do your bidding, it's generally a good idea to make sure no one comes along behind you to mess with it. This is just as valid for shops with multiple administrators as it is for keeping an eye on the bad guys. There are two very good tools built into CentOS for guarding your system. The first of these is aide. Aide is somewhat like tripwire, and can be configured to periodically check your system against a hash of key files for modifications. The second is auditd. The audit subsystem in CentOS will watch your system in real time, based on rules that you set. It will log anything and everything you tell it to, and probably more.
If it's at all possible, you should consider having a central log collection server on a non-public network interface. It's much harder for a malicious user to cover their tracks if the logs are being sent to a remote system they can't access.
1. Aide
Rather than re-invent the wheel telling you how to configure aide, please see http://www.server-world.info/en/note?os=CentOS_7&p=aide instead. However we suggest to replace the crontab entry from that site with this one.
00 01 * * * /usr/sbin/aide --check | mail -s 'Daily Check by AIDE' root
Even if the site mentions CentOS 7, you can use the same instructions for previous CentOS releases.
2. Auditd
To Be Written