CentOS 7 Server Hardening Guide

This guide is based on a minimal CentOS 7 install following the idea that you only install software that you require.

For those familiar with OpenSCAP, you will notice the guide divided into two major sections: System Settings and Services. The first part contains rules that check system settings, where the second part is aimed towards hardening services.

General disclaimer applies: do not implement changes to production systems unless you understand what they do.

1. System Settings – Disk Partitioning and Post installation

1.1 Disk Encryption with Kickstart

The easiest way to encrypt a partition is during Kickstart installation.

This can be achieved by adding the –encrypted and –passphrase= options to the definition of a physical LVM volume.

Our Kickstart template is provided below. Note that the template requires a 32GB disk.

#version=CentOS7.5

# System authorisation information
auth --enableshadow --passalgo=sha512
# Use CDROM installation media
cdrom

ignoredisk --only-use=sda

# Keyboard layouts
keyboard --vckeymap=gb --xlayouts='gb'
# System language
lang en_GB.UTF-8
# SELinux
selinux --enforcing

# Network information
network --bootproto=dhcp --device=eth0 --onboot=on --activate
network --hostname=ks-c7.example.com

# Plaintext root password: PleaseChangeMe
rootpw --iscrypted $6$nS0mBJyS$q/QgCof5unWrT9W3qngTISueSDhDHVNntDqd8sOcgmHp2lq4f/niUbjCmoEzaf3EWQ2x3z/k0eIZaOXkfNtJw/
# System timezone
timezone Europe/London --isUtc
# System bootloader configuration
bootloader --location=mbr --boot-drive=sda --timeout=3
# Partition clearing information
clearpart --all --drives=sda
zerombr 

# Disk partitioning information
#
# Your disk should be 32GB: 1GB for /boot and 30GB for the physical volume
#
# Journal for boot is not required therefore ext2
part /boot --fstype="ext2" --ondisk=sda --size=1024 --label=boot --mkfsoptions="-m 0" --fsoptions="rw,nodev,noexec,nosuid"

# The line below will create a 30GB physical volume
part pv.01  --fstype="lvmpv" --ondisk=sda --size=30720 --encrypted --passphrase=PleaseChangeMeToSomethingElse
volgroup vg_crypto pv.01

# Need the ability to shrink filesystems therefore ext4 over default xfs
logvol /              --fstype="ext4" --size=6144 --vgname=vg_crypto --name=lv_root    --mkfsoptions="-m 1"
logvol /home          --fstype="ext4" --size=2048 --vgname=vg_crypto --name=lv_home    --mkfsoptions="-m 0" --fsoptions="rw,nodev,nosuid"
logvol /tmp           --fstype="ext4" --size=1024 --vgname=vg_crypto --name=lv_tmp     --mkfsoptions="-m 1" --fsoptions="rw,nodev,noexec,nosuid"
logvol /var           --fstype="ext4" --size=4096 --vgname=vg_crypto --name=lv_var     --mkfsoptions="-m 1" --fsoptions="rw,nosuid"
logvol /var/log       --fstype="ext4" --size=1024 --vgname=vg_crypto --name=lv_var-log --mkfsoptions="-m 0" --fsoptions="rw,nodev,noexec,nosuid"
logvol /var/log/audit --fstype="ext4" --size=512  --vgname=vg_crypto --name=lv_var-aud --mkfsoptions="-m 0" --fsoptions="rw,nodev,noexec,nosuid"
logvol /var/tmp       --fstype="ext4" --size=1024 --vgname=vg_crypto --name=lv_var-tmp --mkfsoptions="-m 1" --fsoptions="rw,nodev,noexec,nosuid"
logvol /var/www       --fstype="ext4" --size=1024 --vgname=vg_crypto --name=lv_var-www --mkfsoptions="-m 0" --fsoptions="rw,nodev,nosuid"
logvol swap           --fstype="swap" --size=512  --vgname=vg_crypto --name=lv_swap    --fsoptions="swap"

%packages
@core

%end

1.2 Partition Scheme

Keep the following partitions separate: /boot, /home, /tmp, /var, /var/log, /var/tmp, /var/log/audit, /var/www.

Placing these in their own partitions gives more control over mount options. It also ensures that the system cannot be halted because of some partition running out of disk space.

Splitting off /opt depends on a setup and is generally not useful, but not harmful either.

1.3 Post installation

Backup a LUKS header, where /dev/sda2 is the LUKS encrypted partition:

# cryptsetup luksHeaderBackup /dev/sda2 --header-backup-file /root/luks-header.backup

Ensure that the backup file is stored off-site and then removed from the server.

Make sure that the system is up to date:

# yum update

Remove packages which you don’t require on a server, e.g. firmware of sound cards, firmware of WinTV, wireless drivers etc.

# yum remove alsa-* ivtv-* iwl*firmware aic94xx-firmware

2. System Settings – File Permissions and Masks

2.1 Restrict Partition Mount Options

Partitions should have hardened mount options:

  1. /boot – rw,nodev,noexec,nosuid
  2. /home – rw,nodev,nosuid
  3. /tmp – rw,nodev,noexec,nosuid
  4. /var – rw,nosuid
  5. /var/log – rw,nodev,noexec,nosuid
  6. /var/log/audit – rw,nodev,noexec,nosuid
  7. /var/www – rw,nodev,nosuid

As a rule of thumb, malicious applications usually write to /tmp and then attempt to run whatever was written. A way to prevent this is to mount /tmp on a separate partition with the options noexec, nodev and nosuid enabled.

This will deny binary execution from /tmp, disable any binary to be suid root, and disable any block devices from being created.

The storage location /var/tmp should be bind mounted to /tmp, as having multiple locations for temporary storage is not required:

/tmp /var/tmp none rw,nodev,noexec,nosuid,bind 0 0

The same applies to shared memory /dev/shm:

tmpfs /dev/shm tmpfs rw,nodev,noexec,nosuid 0 0

The proc pseudo-filesystem /proc should be mounted with hidepid. When setting hidepid to 2, directories entries in /proc will hidden.

proc /proc proc rw,hidepid=2 0 0

Harden removeable media mounts by adding nodev, noexec and nosuid, e.g.:

/dev/cdrom /mnt/cdrom iso9660 ro,noexec,nosuid,nodev,noauto 0 0

2.2 Restrict Dynamic Mounting and Unmounting of Filesystems

Add the following to /etc/modprobe.d/hardening.conf to disable uncommon filesystems:

install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true

Depending on a setup (if you don’t run clusters, NFS, CIFS etc), you may consider disabling the following too:

install fat /bin/true
install vfat /bin/true
install cifs /bin/true
install nfs /bin/true
install nfsv3 /bin/true
install nfsv4 /bin/true
install gfs2 /bin/true

It is wise to leave ext4, xfs and btrfs enabled at all times.

2.3 Prevent Users Mounting USB Storage

Add the following to /etc/modprobe.d/hardening.conf to disable modprobe loading of USB and FireWire storage drivers:

blacklist usb-storage
blacklist firewire-core
install usb-storage /bin/true

Disable USB authorisation. Create a file /opt/usb-auth.sh with the following content:

#!/bin/bash
echo 0 > /sys/bus/usb/devices/usb1/authorized
echo 0 > /sys/bus/usb/devices/usb1/authorized_default

If more than one USB device is available, then add them all. Create a service file /etc/systemd/system/usb-auth.service with the following content:

[Unit]
Description=Disable USB auth
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/bin/bash /opt/usb-auth.sh

[Install]
WantedBy=multi-user.target

Set permissions, enable and start the service:

# chmod 0700 /opt/usb-auth.sh
# systemctl enable usb-auth.service
# systemctl start usb-auth.service

If required, disable kernel support for USB via bootloader configuration. To do so, append nousb to the kernel line GRUB_CMDLINE_LINUX in /etc/default/grub and generate the Grub2 configuration file:

# grub2-mkconfig -o /boot/grub2/grub.cfg

Note that disabling all kernel support for USB will likely cause problems for systems with USB-based keyboards etc.

2.4 Restrict Programs from Dangerous Execution Patterns

Configure /etc/sysctl.conf with the following:

# Disable core dumps
fs.suid_dumpable = 0

# Disable System Request debugging functionality
kernel.sysrq = 0

# Restrict access to kernel logs
kernel.dmesg_restrict = 1

# Enable ExecShield protection - not available on CentOS 7
# kernel.exec-shield = 1

# Randomise memory space
kernel.randomize_va_space = 2

# Hide kernel pointers
kernel.kptr_restrict = 2

Load sysctl settings:

# sysctp -p

2.5 Set UMASK 027

The following files require umask hardening: /etc/bashrc, /etc/csh.cshrc, /etc/init.d/functions and /etc/profile.

Sed one-liner:

# sed -i -e 's/umask 022/umask 027/g' -e 's/umask 002/umask 027/g' /etc/bashrc
# sed -i -e 's/umask 022/umask 027/g' -e 's/umask 002/umask 027/g' /etc/csh.cshrc
# sed -i -e 's/umask 022/umask 027/g' -e 's/umask 002/umask 027/g' /etc/profile
# sed -i -e 's/umask 022/umask 027/g' -e 's/umask 002/umask 027/g' /etc/init.d/functions

2.6 Disable Core Dumps

Open /etc/security/limits.conf and set the following:

*  hard  core  0

2.7 Set Security Limits to Prevent DoS

Add the following to /etc/security/limits.conf to enforce sensible security limits:

# 4096 is a good starting point
*      soft   nofile    4096
*      hard   nofile    65536
*      soft   nproc     4096
*      hard   nproc     4096
*      soft   locks     4096
*      hard   locks     4096
*      soft   stack     10240
*      hard   stack     32768
*      soft   memlock   64
*      hard   memlock   64
*      hard   maxlogins 10

# Soft limit 32GB, hard 64GB
*      soft   fsize     33554432
*      hard   fsize     67108864

# Limits for root
root   soft   nofile    4096
root   hard   nofile    65536
root   soft   nproc     4096
root   hard   nproc     4096
root   soft   stack     10240
root   hard   stack     32768
root   soft   fsize     33554432

2.8 Verify Permissions of Files

Ensure that all files are owned by a user:

# find / -ignore_readdir_race -nouser -print -exec chown root {} \;

Ensure that all files are owned by a group:

# find / -ignore_readdir_race -nogroup -print -exec chgrp root {} \;

If required, a specific path can be excluded from the search, e.g.:

# find / -ignore_readdir_race -not -path "/proc/*" -nouser -print -exec chown root {} \;

Automate the process by creating a cron file /etc/cron.daily/unowned_files with the following content:

#!/bin/bash
find / -ignore_readdir_race \( -nouser -print -exec chown root {} \; \) , \( -nogroup -print -exec chgrp root {} \; \)

Set ownership and permissions:

# chown root:root /etc/cron.daily/unowned_files
# chmod 0700 /etc/cron.daily/unowned_files

2.9 Monitor SUID/GUID Files

Search for setuid/setgid files and identify if all are required:

# find / -xdev -type f -perm -4000 -o -perm -2000

3. System Settings – Firewall and Network Configuration

3.1 Firewall

Setting the default firewalld zone to drop makes any packets which are not explicitly permitted to be rejected.

# sed -i "s/DefaultZone=.*/DefaultZone=drop/g" /etc/firewalld/firewalld.conf

Unless firewalld is required, mask it and replace with iptables:

# systemctl stop firewalld.service
# systemctl mask firewalld.service
# systemctl daemon-reload
# yum install iptables-services
# systemctl enable iptables.service ip6tables.service

Add the following to /etc/sysconfig/iptables to allow only minimal outgoing traffic (DNS, NTP, HTTP/S and SMTPS):

*filter
-F INPUT
-F OUTPUT
-F FORWARD
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-A INPUT -i lo -m comment --comment local -j ACCEPT
-A INPUT -d 127.0.0.0/8 ! -i lo -j REJECT --reject-with icmp-port-unreachable
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 22 -s 10.0.0.0/8 -j ACCEPT
-A INPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 22 -s 172.16.0.0/12 -j ACCEPT
-A INPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 22 -s 192.168.0.0/16 -j ACCEPT
-A INPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 22 -j ACCEPT
-A INPUT -j DROP
-A OUTPUT -d 127.0.0.0/8 -o lo -m comment --comment local -j ACCEPT
-A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -p icmp -m icmp --icmp-type any -j ACCEPT
-A OUTPUT -p udp -m udp -m conntrack --ctstate NEW --dport 53 -j ACCEPT
-A OUTPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 53 -j ACCEPT
-A OUTPUT -p udp -m udp -m conntrack --ctstate NEW --dport 123 -j ACCEPT
-A OUTPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 80 -j ACCEPT
-A OUTPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 443 -j ACCEPT
-A OUTPUT -p tcp -m tcp -m conntrack --ctstate NEW --dport 587 -j ACCEPT
-A OUTPUT -j LOG --log-prefix "iptables_output "
-A OUTPUT -j REJECT --reject-with icmp-port-unreachable
COMMIT

Note that the rule allowing all incoming SSH traffic should be removed restricting access to an IP whitelist only, or hiding SSH behind a VPN.

Ideally, outgoing rules should be hardened by restricting access to local DNS, NTP and SMTP servers only. If a local patching system is used (e.g. Red Hat Satellite), then HTTP/S traffic can also be further hardened, depending on a set up.

Add the following to /etc/sysconfig/ip6tables to deny all IPv6:

*filter
-F INPUT
-F OUTPUT
-F FORWARD
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP
COMMIT

Apply configurations:

# iptables-restore < /etc/sysconfig/iptables
# ip6tables-restore < /etc/sysconfig/ip6tables

3.2 TCP Wrappers

Open /etc/hosts.allow and allow localhost traffic and SSH:

ALL: 127.0.0.1
sshd: ALL

The file /etc/hosts.deny should be configured to deny all by default:

ALL: ALL

3.3 Kernel Parameters Which Affect Networking

Open /etc/sysctl.conf and add the following:

# Disable packet forwarding
net.ipv4.ip_forward = 0

# Disable redirects, not a router
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Enable source validation by reversed path
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Log packets with impossible addresses to kernel log
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# Disable ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Ignore bogus ICMP errors
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Against SYN flood attacks
net.ipv4.tcp_syncookies = 1

# Turning off timestamps could improve security but degrade performance.
# TCP timestamps are used to improve performance as well as protect against
# late packets messing up your data flow. A side effect of this feature is 
# that the uptime of the host can sometimes be computed.
# If you disable TCP timestamps, you should expect worse performance 
# and less reliable connections.
net.ipv4.tcp_timestamps = 1

# Disable IPv6 unless required
net.ipv6.conf.lo.disable_ipv6 = 1
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# Do not accept router advertisements
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0

3.4 Kernel Modules Which Affect Networking

Open /etc/modprobe.d/hardening.conf and disable Bluetooth kernel modules:

install bnep /bin/true
install bluetooth /bin/true
install btusb /bin/true
install net-pf-31 /bin/true

Also disable AppleTalk:

install appletalk /bin/true

Unless required, disable support for IPv6:

options ipv6 disable=1

Disable (uncommon) protocols:

install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true

Since we’re looking at server security, wireless shouldn’t be an issue, therefore we can disable all the wireless drivers.

# for i in $(find /lib/modules/$(uname -r)/kernel/drivers/net/wireless -name "*.ko" -type f);do \
  echo blacklist "$i" >>/etc/modprobe.d/hardening-wireless.conf;done

3.5 Disable Radios

Disable radios (wifi and wwan):

# nmcli radio all off

3.6 Disable Zeroconf Networking

Open /etc/sysconfig/network and add the following:

NOZEROCONF=yes

3.7 Disable Interface Usage of IPv6

Open /etc/sysconfig/network and add the following:

NETWORKING_IPV6=no
IPV6INIT=no

3.8 Network Sniffer

The server should not be acting as a network sniffer and capturing packages. Run the following to determine if any interface is running in promiscuous mode:

# ip link | grep PROMISC

3.9 Secure VPN Connection

Install the libreswan package if implementation of IPsec and IKE is required.

# yum install libreswan

3.10 Disable DHCP Client

Manual assignment of IP addresses provides a greater degree of management.

For each network interface that is available on the server, open a corresponding file /etc/sysconfig/network-scripts/ifcfg-interface and configure the following parameters:

BOOTPROTO=none
IPADDR=
NETMASK=
GATEWAY=

4. System Settings – SELinux

Ensure that SELinux is not disabled in /etc/default/grub, and verify that the state is enforcing:

# sestatus

5. System Settings – Account and Access Control

5.1 Delete Unused Accounts and Groups

Remove any account which is not required, e.g.:

# userdel -r adm
# userdel -r ftp
# userdel -r games
# userdel -r lp

Remove any group which is not required, e.g.:

# groupdel games

5.2 Disable Direct root Login

# echo > /etc/securetty

5.3 Enable Secure (high quality) Password Policy

Note that running authconfig will overwrite the PAM configuration files destroying any manually made changes. Make sure that you have a backup.

Secure password policy rules are outlined below.

  1. Minimum length of a password – 16.
  2. Minimum number of character classes in a password – 4.
  3. Maximum number of same consecutive characters in a password – 2.
  4. Maximum number of consecutive characters of same class in a password – 2.
  5. Require at least one lowercase and one uppercase characters in a password.
  6. Require at least one digit in a password.
  7. Require at least one other character in a password.

The following command will enable SHA512 as well as set the above password requirements:

# authconfig --passalgo=sha512 \
 --passminlen=16 \
 --passminclass=4 \
 --passmaxrepeat=2 \
 --passmaxclassrepeat=2 \
 --enablereqlower \
 --enablerequpper \
 --enablereqdigit \
 --enablereqother \
 --update

Open /etc/security/pwquality.conf and add the following:

difok = 8
gecoscheck = 1

These will ensure that 8 characters in the new password must not be present in the old password, and will check for the words from the passwd entry GECOS string of the user.

5.4 Prevent Log In to Accounts With Empty Password

Remove any instances of nullok from /etc/pam.d/system-auth and /etc/pam.d/password-auth to prevent logins with empty passwords.

Sed one-liner:

# sed -i 's/\<nullok\>//g' /etc/pam.d/system-auth /etc/pam.d/system-auth-ac
# sed -i 's/\<nullok\>//g' /etc/pam.d/password-auth /etc/pam.d/password-auth-ac

5.5 Set Account Expiration Following Inactivity

Disable accounts as soon as the password has expired.

Open /etc/default/useradd and set the following:

INACTIVE=0

Sed one-liner:

# sed -i 's/^INACTIVE.*/INACTIVE=0/' /etc/default/useradd

5.6 Secure Pasword Policy

Open /etc/login.defs and set the following:

PASS_MAX_DAYS 60
PASS_MIN_DAYS 1
PASS_MIN_LEN 14
PASS_WARN_AGE 14

Sed one-liner:

# sed -i -e 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 60/' \
  -e 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' \
  -e 's/^PASS_MIN_LEN.*/PASS_MIN_LEN 14/' \
  -e 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 14/' /etc/login.defs

5.7 Log Failed Login Attemps

Open /etc/login.defs and enable logging:

FAILLOG_ENAB yes

Also add a delay in seconds before being allowed another attempt after a login failure:

FAIL_DELAY 4

5.8 Ensure Home Directories are Created for New Users

Open /etc/login.defs and configure:

CREATE_HOME yes

5.9 Verify All Account Password Hashes are Shadowed

The command below should return “x”:

# cut -d: -f2 /etc/passwd|uniq

5.10 Set Deny and Lockout Time for Failed Password Attempts

Add the following line immediately before the pam_unix.so statement in the AUTH section of /etc/pam.d/system-auth and /etc/pam.d/password-auth:

auth required pam_faillock.so preauth silent deny=3 unlock_time=900 fail_interval=900

Add the following line immediately after the pam_unix.so statement in the AUTH section of /etc/pam.d/system-auth and /etc/pam.d/password-auth:

auth [default=die] pam_faillock.so authfail deny=3 unlock_time=900 fail_interval=900

Add the following line immediately before the pam_unix.so statement in the ACCOUNT section of /etc/pam.d/system-auth and /etc/pam.d/password-auth:

account required pam_faillock.so

The content of the file /etc/pam.d/system-auth can be seen below.

#%PAM-1.0
auth        required      pam_env.so
auth        required      pam_faillock.so preauth silent deny=3 unlock_time=900 fail_interval=900
auth        sufficient    pam_unix.so  try_first_pass
auth        [default=die] pam_faillock.so authfail deny=3 unlock_time=900 fail_interval=900
auth        requisite     pam_succeed_if.so uid >= 1000 quiet_success
auth        required      pam_deny.so

account     required      pam_faillock.so
account     required      pam_unix.so
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 1000 quiet
account     required      pam_permit.so

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password    sufficient    pam_unix.so sha512 shadow  try_first_pass use_authtok remember=5
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
-session    optional      pam_systemd.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so

Also, do not allow users to reuse recent passwords by adding the remember option.

Make /etc/pam.d/system-auth and /etc/pam.d/password-auth configurations immutable so that they don’t get overwritten when authconfig is run:

# chattr +i /etc/pam.d/system-auth /etc/pam.d/password-auth

Accounts will get locked after 3 failed login attemtps:

login[]: pam_faillock(login:auth): Consecutive login failures for user tomas account temporarily locked 

Use the following to clear user’s fail count:

# faillock --user tomas --reset

5.11 Set Boot Loader Password

Prevent users from entering the grub command line and edit menu entries:

# grub2-setpassword
# grub2-mkconfig -o /boot/grub2/grub.cfg

This will create the file /boot/grub2/user.cfg if one is not already present, which will contain the hashed Grub2 bootloader password.

Verify permissions of /boot/grub2/grub.cfg:

# chmod 0600 /boot/grub2/grub.cfg

5.12 Password-protect Single User Mode

CentOS 7 single user mode is password protected by the root password by default as part of the design of Grub2 and systemd.

5.13 Ensure Users Re-Authenticate for Privilege Escalation

The NOPASSWD tag allows a user to execute commands using sudo without having to provide a password. While this may sometimes be useful it is also dangerious.

Ensure that the NOPASSWD tag does not exist in /etc/sudoers configuration file or /etc/sudoers.d/.

5.14 Multiple Console Screens and Console Locking

Install the screen package to be able to emulate multiple console windows:

# yum install screen

Install the vlock package to enable console screen locking:

# yum install vlock

5.15 Disable Ctrl-Alt-Del Reboot Activation

Prevent a locally logged-in console user from rebooting the system when Ctrl-Alt-Del is pressed:

# systemctl mask ctrl-alt-del.target

5.16 Warning Banners for System Access

Add the following line to the files /etc/issue and /etc/issue.net:

Unauthorised access prohibited. Logs are recorded and monitored.

5.17 Set Interactive Session Timeout

Open /etc/profile and set:

readonly TMOUT=900

5.18 Two Factor Authentication

The recent version of OpenSSH server allows to chain several authentication methods, meaning that all of them have to be satisfied in order for a user to log in successfully.

Adding the following line to /etc/ssh/sshd_config would require a user to authenticate with a key first, and then also provide a password.

AuthenticationMethods publickey,password

This is by definition a two factor authentication: the key file is something that a user has, and the account password is something that a user knows.

Alternatively, two factor authentication for SSH can be set up by using Google Authenticator.

5.19 Configure History File Size

Open /etc/profile and set the number of commands to remember in the command history to 5000:

HISTSIZE=5000

Sed one-liner:

# sed -i 's/HISTSIZE=.*/HISTSIZE=5000/g' /etc/profile

6. System Settings – System Accounting with auditd

6.1 Auditd Configuration

Open /etc/audit/auditd.conf and configure the following:

local_events = yes
write_logs = yes
log_file = /var/log/audit/audit.log
max_log_file = 25
num_logs = 10
max_log_file_action = rotate
space_left = 30
space_left_action = email
admin_space_left = 10
admin_space_left_action = email
disk_full_action = suspend
disk_error_action = suspend
action_mail_acct = [email protected]
flush = data

The above auditd configuration should never use more than 250MB of disk space (10x25MB=250MB) on /var/log/audit.

Set admin_space_left_action=single if you want to cause the system to switch to single user mode for corrective action rather than send an email.

Automatically rotating logs (max_log_file_action=rotate) minimises the chances of the system unexpectedly running out of disk space by being filled up with log data.

We need to ensure that audit event data is fully synchronised (flush=data) with the log files on the disk .

6.2 Auditd Rules

System audit rules must have mode 0640 or less permissive and owned by the root user:

# chown root:root /etc/audit/rules.d/audit.rules
# chmod 0640 /etc/audit/rules.d/audit.rules

Open /etc/audit/rules.d/audit.rules and add the following:

# Delete all currently loaded rules
-D

# Set kernel buffer size
-b 8192

# Set the action that is performed when a critical error is detected.
# Failure modes: 0=silent 1=printk 2=panic
-f 1

# Record attempts to alter the localtime file
-w /etc/localtime -p wa -k audit_time_rules

# Record events that modify user/group information
-w /etc/group -p wa -k audit_rules_usergroup_modification
-w /etc/passwd -p wa -k audit_rules_usergroup_modification
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification
-w /etc/shadow -p wa -k audit_rules_usergroup_modification
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification

# Record events that modify the system's network environment
-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
-w /etc/issue -p wa -k audit_rules_networkconfig_modification
-w /etc/hosts -p wa -k audit_rules_networkconfig_modification
-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
-a always,exit -F arch=b32 -S sethostname -S setdomainname -k audit_rules_networkconfig_modification
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k audit_rules_networkconfig_modification

# Record events that modify the system's mandatory access controls
-w /etc/selinux/ -p wa -k MAC-policy

# Record attempts to alter logon and logout events
-w /var/log/tallylog -p wa -k logins
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock/ -p wa -k logins

# Record attempts to alter process and session initiation information
-w /var/log/btmp -p wa -k session
-w /var/log/wtmp -p wa -k session
-w /var/run/utmp -p wa -k session

# Ensure auditd collects information on kernel module loading and unloading
-w /usr/sbin/insmod -p x -k modules
-w /usr/sbin/modprobe -p x -k modules
-w /usr/sbin/rmmod -p x -k modules
-a always,exit -F arch=b64 -S init_module -S delete_module -k modules

# Ensure auditd collects system administrator actions
-w /etc/sudoers -p wa -k actions
-w /etc/sudoers.d/ -p wa -k actions

# Record attempts to alter time through adjtimex
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k audit_time_rules

# Record attempts to alter time through settimeofday
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k audit_time_rules

# Record attempts to alter time through clock_settime
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -k time-change

# Record attempts to alter time through clock_settime
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -k time-change

# Record events that modify the system's discretionary access controls
-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod

# Ensure auditd collects unauthorised access attempts to files (unsuccessful)
-a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access
-a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access
-a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access
-a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access

# Ensure auditd collects information on exporting to media (successful)
-a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=4294967295 -k export
-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=4294967295 -k export

# Ensure auditd collects file deletion events by user
-a always,exit -F arch=b32 -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete
-a always,exit -F arch=b64 -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete

# Ensure auditd collects information on the use of privileged commands
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
-a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/pkexec -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/screen -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/wall -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/write -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/lib64/dbus-1/dbus-daemon-launch-helper -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/libexec/utempter/utempter -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/lib/polkit-1/polkit-agent-helper-1 -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/netreport -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/postdrop -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/postqueue -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/restorecon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
-a always,exit -F path=/usr/sbin/semanage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
-a always,exit -F path=/usr/sbin/setsebool -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
-a always,exit -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged

# Make the auditd configuration immutable.
# The configuration can only be changed by rebooting the machine.
-e 2

The auditd service does not include the ability to send audit records to a centralised server for management directly.

It does, however, include a plug-in for audit event multiplexor to pass audit records to the local syslog server.

To do so, open the file /etc/audisp/plugins.d/syslog.conf and set:

active = yes

Enable and start the service:

# systemctl enable auditd.service
# systemctl start auditd.service

6.3. Enable Kernel Auditing

Open /etc/default/grub and append the following parameter to the kernel boot line GRUB_CMDLINE_LINUX:

audit=1

Update Grub2 configuration to reflect changes:

# grub2-mkconfig -o /boot/grub2/grub.cfg

7. System Settings – Software Integrity Checking

7.1 Advanced Intrusion Detection Environment (AIDE)

Install AIDE:

# yum install aide

Build AIDE database:

# /usr/sbin/aide --init

By default, the database will be written to the file /var/lib/aide/aide.db.new.gz.

# cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

Storing the database and the configuration file /etc/aide.conf (or SHA2 hashes of the files) in a secure location provides additional assurance about their integrity.

Check AIDE database:

# /usr/sbin/aide --check

By default, AIDE does not install itself for periodic execution. Configure periodic execution of AIDE by adding to cron:

# echo "30 4 * * * root /usr/sbin/aide --check|mail -s 'AIDE' [email protected]" >> /etc/crontab

Periodically running AIDE is necessary in order to reveal system changes.

7.2 Tripwire

Open Source Tripwire is an alternative to AIDE. It is recommended to use one or another, but not both.

Install Tripwire from the EPEL repository:

# yum install epel-release
# yum install tripwire
# /usr/sbin/tripwire-setup-keyfiles

The Tripwire configuration file is /etc/tripwire/twcfg.txt and the policy file is /etc/tripwire/twpol.txt. These can be edited and configured to match the system Tripwire is installed on, see this blog post for more details.

Initialise the database to implement the policy:

# tripwire --init

Check for policy violations:

# tripwire --check

Tripwire adds itself to /etc/cron.daily/ for daily execution therefore no extra configuration is required.

7.3 Prelink

Prelinking is done by the prelink package, which is not installed by default.

# yum install prelink

To disable prelinking, open the file /etc/sysconfig/prelink and set the following:

PRELINKING=no

Sed one-liner:

# sed -i 's/PRELINKING.*/PRELINKING=no/g' /etc/sysconfig/prelink

Disable existing prelinking on all system files:

# prelink -ua

8. System Settings – Logging and Message Forwarding

8.1 Configure Persistent Journald Storage

By default, journal stores log files only in memory or a small ring-buffer in the directory /run/log/journal. This is sufficient to show recent log history with journalctl, but logs aren’t saved permanently. Enabling persistent journal storage ensures that comprehensive data is available after system reboot.

Open the file /etc/systemd/journald.conf and put the following:

[Journal]
Storage=persistent

# How much disk space the journal may use up at most
SystemMaxUse=256M

# How much disk space systemd-journald shall leave free for other uses
SystemKeepFree=512M

# How large individual journal files may grow at most
SystemMaxFileSize=32M

Restart the service:

# systemctl daemon-reload
# systemctl restart systemd-journald

8.2 Configure Message Forwarding to Remote Server

Depending on your setup, open /etc/rsyslog.conf and add the following to forward messages to a some remote server:

*.* @graylog.example.com:514

Here *.* stands for facility.severity. Note that a single @ sends logs over UDP, where a double @ sends logs using TCP.

8.3 Logwatch

Logwatch is a customisable log-monitoring system.

# yum install logwatch

Logwatch adds itself to /etc/cron.daily/ for daily execution therefore no configuration is mandatory.

9. System Settings – Security Software

9.1 Malware Scanners

Install Rkhunter and ClamAV:

# yum install epel-release
# yum install rkhunter clamav clamav-update
# rkhunter --update
# rkhunter --propupd
# freshclam -v

Rkhunter adds itself to /etc/cron.daily/ for daily execution therefore no configuration is required. ClamAV scans should be tailored to individual needs.

9.2 Arpwatch

Arpwatch is a tool used to monitor ARP activity of a local network (ARP spoofing detection), therefore it is unlikely one will use it in the cloud, however, it is still worth mentioning that the tools exist.

Be aware of the configuration file /etc/sysconfig/arpwatch which you use to set the email address where to send the reports.

9.3 Commercial AV

Consider installing a commercial AV product that provides real-time on-access scanning capabilities.

9.4 Grsecurity

Grsecurity is an extensive security enhancement to the Linux kernel. Although it isn’t free nowadays, the software is still worth mentioning.

The company behind Grsecurity stopped publicly distributing stable patches back in 2015, with an exception of the test series continuing to be available to the public in order to avoid impact to the Gentoo Hardened and Arch Linux communities.

Two years later, the company decided to cease free distribution of the test patches as well, therefore as of 2017, Grsecurity software is available to paying customers only.

10. System Settings – OS Update Installation

Install the package yum-utils for better consistency checking of the package database.

# yum install yum-utils

Configure automatic package updates via yum-cron.

# yum install yum-cron

Add the following to /etc/yum/yum-cron.conf to get notified via email when new updates are available:

update_cmd = default
update_messages = yes
download_updates = no	
apply_updates = no
emit_via = email	
email_from = [email protected]
email_to = [email protected]
email_host = localhost

Add the following to /etc/yum/yum-cron-hourly.conf to check for bugfix-related updates every hour and automatically download and install them:

update_cmd = minimal # yum --bugfix update-minimal
update_messages = yes
download_updates = yes
apply_updates = yes
emit_via = stdio

Note: security information is provided by RedHat only. When you query a repository that is provided by CentOS it does not supply security metadata (however the EPEL repository does have security metadata).

Therefore if you decide to use update_cmd = security, yum will always tell you that nothing from CentOS needs a security update.

Enable and start the service:

# systemctl enable yum-cron.service
# systemctl start yum-cron.service

11. System Settings – Process Accounting

The package psacct contain utilities for monitoring process activities:

  1. ac – displays statistics about how long users have been logged on.
  2. lastcomm – displays information about previously executed commands.
  3. accton – turns process accounting on or off.
  4. sa – summarises information about previously executed commands.

Install and enable the service:

# yum install psacct
# systemctl enable psacct.service
# systemctl start psacct.service

1. Services – SSH Server

Create a group for SSH access as well as some regular user account who will be a member of the group:

# groupadd ssh-users
# useradd -m -s /bin/bash -G ssh-users tomas

Generate SSH keys for the user:

# su - tomas
$ mkdir --mode=0700 ~/.ssh
$ ssh-keygen -b 4096 -t rsa -C "tomas" -f ~/.ssh/id_rsa

Generate SSH host keys:

# ssh-keygen -b 4096 -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key
# ssh-keygen -b 1024 -t dsa -N "" -f /etc/ssh/ssh_host_dsa_key
# ssh-keygen -b 521 -t ecdsa -N "" -f /etc/ssh/ssh_host_ecdsa_key
# ssh-keygen -t ed25519 -N "" -f /etc/ssh/ssh_host_ed25519_key

For RSA keys, 2048 bits is considered sufficient. DSA keys must be exactly 1024 bits as specified by FIPS 186-2.

For ECDSA keys, the -b flag determines the key length by selecting from one of three elliptic curve sizes: 256, 384 or 521 bits. ED25519 keys have a fixed length and the -b flag is ignored.

The host can be impersonated if an unauthorised user obtains the private SSH host key file, therefore ensure that permissions of /etc/ssh/*_key are properly set:

# chmod 0600 /etc/ssh/*_key

Configure /etc/ssh/sshd_config with the following:

# SSH port.
Port 22

# Listen on IPv4 only.
ListenAddress 0.0.0.0

# Protocol version 1 has been exposed.
Protocol 2

#
# OpenSSH cipher-related release notes.
# OpenSSH 6.2: added support for AES-GCM authenticated encryption. 
# The cipher is available as [email protected] and [email protected].
# OpenSSH 6.5: added new cipher [email protected].
# OpenSSH 6.7: removed unsafe algorithms. CBC ciphers are disabled by default:
# aes128-cbc, aes192-cbc, aes256-cbc, 3des-cbc, blowfish-cbc, cast128-cbc.
# OpenSSH 6.9: promoted [email protected] to be the default cipher.
#
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr

#
# OpenSSH 6.2: added support for the UMAC-128 MAC as [email protected] 
# and [email protected]. The latter being an encrypt-then-mac mode.
# Do not use umac-64 or umac-64-etm because of a small 64 bit tag size.
# Do not use any SHA1 (e.g. hmac-sha1, [email protected]) MACs 
# because of a weak hashing algorithm. 
# Do not use hmac-sha2-256, hmac-sha2-512 or [email protected] 
# because of an encrypt-and-MAC mode. See the link below:
# https://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac
#
MACs [email protected],[email protected],[email protected]

#
# OpenSSH 6.5: added support for ssh-ed25519. It offers better security 
# than ECDSA and DSA.
# OpenSSH 7.0: disabled support for ssh-dss. 
# OpenSSH 7.2: added support for rsa-sha2-512 and rsa-sha2-256.
#
HostKeyAlgorithms ssh-ed25519,[email protected],ssh-rsa,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,[email protected],[email protected],[email protected],[email protected],[email protected]

#
# OpenSSH 6.5: added support for key exchange using elliptic-curve
# Diffie Hellman in Daniel Bernstein's Curve25519.
# OpenSSH 7.3: added support for diffie-hellman-group14-sha256,
# diffie-hellman-group16-sha512 and diffie-hellman-group18-sha512.
#
KexAlgorithms [email protected],diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,diffie-hellman-group14-sha256

# HostKeys for protocol version 2.
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ed25519_key

# Disabled because uses a small 1024 bit key.
#HostKey /etc/ssh/ssh_host_dsa_key

# Disabled because uses weak elliptic curves.
# See: https://safecurves.cr.yp.to/
#HostKey /etc/ssh/ssh_host_ecdsa_key


# INFO is a basic logging level that will capture user login/logout activity.
# DEBUG logging level is not recommended for production servers.
LogLevel INFO

# Disconnect if no successful login is made in 60 seconds.
LoginGraceTime 60

# Do not permit root logins via SSH.
PermitRootLogin no

# Check file modes and ownership of the user's files before login.
StrictModes yes

# Close TCP socket after 2 invalid login attempts.
MaxAuthTries 2

# The maximum number of sessions per network connection.
MaxSessions 3

# User/group permissions.
AllowUsers
AllowGroups ssh-users
DenyUsers root
DenyGroups root

# Password and public key authentications.
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
AuthorizedKeysFile  .ssh/authorized_keys

# Disable unused authentications mechanisms.
RSAAuthentication no # DEPRECATED
RhostsRSAAuthentication no # DEPRECATED
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
HostbasedAuthentication no
IgnoreUserKnownHosts yes

# Disable insecure access via rhosts files.
IgnoreRhosts yes

AllowAgentForwarding no
AllowTcpForwarding no

# Disable X Forwarding.
X11Forwarding no

# Disable message of the day but print last log.
PrintMotd no
PrintLastLog yes

# Show banner.
Banner /etc/issue

# Do not send TCP keepalive messages.
TCPKeepAlive no

# Default for new installations.
UsePrivilegeSeparation sandbox

# Prevent users from potentially bypassing some access restrictions.
PermitUserEnvironment no

# Disable compression.
Compression no

# Disconnect the client if no activity has been detected for 900 seconds.
ClientAliveInterval 900
ClientAliveCountMax 0

# Do not look up the remote hostname.
UseDNS no

UsePAM yes

You can use the ssh-audit tool to test your SSH server configuration.

In case you want to change the default SSH port to something else, you will need to tell SELinux about it.

# yum install policycoreutils-python

For example, to allow SSH server to listen on TCP 2222, do the following:

# semanage port -a -t ssh_port_t 2222 -p tcp

Ensure that the firewall allows incoming traffic on the new SSH port and restart the sshd service.

2. Service – Network Time Protocol

CentOS 7 should come with Chrony, make sure that the service is enabled:

# systemctl enable chronyd.service

3. Services – Mail Server

3.1 Postfix

Postfix should be installed and enabled already. In case it isn’t, the do the following:

# yum install postfix
# systemctl enable postfix.service

Open /etc/postfix/main.cf and configure the following to act as a null client:

smtpd_banner = $myhostname ESMTP
inet_interfaces = loopback-only
inet_protocols = ipv4
mydestination =
local_transport = error: local delivery disabled
unknown_local_recipient_reject_code = 550
mynetworks = 127.0.0.0/8
relayhost = [mail.example.com]:587

Optionally (depending on your setup), you can configure Postfix to use authentication:

# yum install cyrus-sasl-plain

Open /etc/postfix/main.cf and add the following:

smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_CApath = /etc/ssl/certs
smtp_use_tls = yes

Open /etc/postfix/sasl_passwd and put authentication credentials in a format of:

[mail.example.com]:587 [email protected]:password

Set permissions and create a database file:

# chmod 0600 /etc/postfix/sasl_passwd
# postmap /etc/postfix/sasl_passwd

Restart the service and ensure that firewall allows outgoing traffic to the SMTP relay server.

3.2 Mail Distribution to Active Mail Accounts

Configure the file /etc/aliases to have a forward rule for the root user.

4. Services – Remove Obsolete Services

None of these should be installed on CentOS 7 minimal:

# yum remove xinetd telnet-server rsh-server \
  telnet rsh ypbind ypserv tfsp-server bind \
  vsfptd dovecot squid net-snmpd talk-server talk

Check all enabled services:

# systemctl list-unit-files --type=service|grep enabled

Disable kernel dump service:

# systemctl disable kdump.service
# systemctl mask kdump.service

Disable everything that is not required, e.g.:

# systemctl disable tuned.service

5. Services – Restrict at and cron to Authorised Users

If the file cron.allow exists, then only users listed in the file are allowed to use cron, and the cron.deny file is ignored.

# echo root > /etc/cron.allow
# echo root > /etc/at.allow
# rm -f /etc/at.deny /etc/cron.deny

Note that the root user can always use cron, regardless of the usernames listed in the access control files.

6. Services – Disable X Windows Startup

This can be achieved by setting a default target:

# systemctl set-default multi-user.target

7. Services – Fail2ban

Install Fail2ban from the EPEL repository:

# yum install epel-release
# yum install fail2ban

If using iptables rather than firewalld, open the file /etc/fail2ban/jail.d/00-firewalld.conf and comment out the following line:

#banaction = firewallcmd-ipset

Fail2Ban uses /etc/fail2ban/jail.conf. Configuration snippet for SSH is provided below:

[sshd]
port    = ssh
enabled = true
ignoreip = 10.8.8.61
bantime  = 600
maxretry = 5

If you run SSH on a non-default port, you can change the port value to any positive integer and then enable the jail.

# systemctl enable fail2ban.service
# systemctl start fail2ban.service

8. Services – Sysstat to Collect Performance Activity

Sysstat may provide useful insight into system usage and performance, however, unless used, the service should be disabled, or not installed at all.

# yum install sysstat
# systemctl enable sysstat.service
# systemctl start sysstat.service

References

OpenSCAP Security Guide

NSA Guide to the Secure Configuration of Red Hat Enterprise Linux 5

https://highon.coffee/blog/security-harden-centos-7/

https://linux-audit.com/linux-system-hardening-adding-hidepid-to-proc/

https://www.openssh.com/releasenotes.html

Secure Secure Shell

What is pam_faillock and how to use it in Red Hat Enterprise Linux

Revision

This guide was last updated on 26/03/2018.

53 thoughts on “CentOS 7 Server Hardening Guide

  1. screen isn’t a console screen locking utility. vlock is. Interestingly, this error is also in the DISA STIGs.

  2. You could possibly roll your two commands into one @ 2.8.

    # find / -ignore_readdir_race -nouser -print -exec chown root {} \;
    # find / -ignore_readdir_race -nogroup -print -exec chgrp root {} \;

    would become

    # find / -ignore_readdir_race \
    > \( -nouser -print -exec chown root {} \; \) , \
    > \( -nogroup -print -exec chgrp root {} \; \)

    Or if you want it all on one line

    # find / -ignore_readdir_race \( -nouser -print -exec chown root {} \; \) , \( -nogroup -print -exec chgrp root {} \; \)

    This should only then traverse the filesystem once to accomplish both tasks

  3. Hi Tomas,I saw the following:
    “Make /etc/pam.d/system-auth and /etc/pam.d/password-auth configurations immutable so that they don’t get overwritten when authconfig is run”
    But as per Red Hat’s hardening guide we should create symbolic links:
    When modifying authentication configuration using the authconfig utility, the system-auth and password-auth files are overwritten with the settings from the authconfig utility. This can be avoided by creating symbolic links in place of the configuration files, which authconfig recognizes and does not overwrite.

    Source – Section 4.1.2 from https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/chap-hardening_your_system_with_tools_and_services#sect-Security_Guide-Workstation_Security-Account_Locking

    • Hi, thanks, yes, this is another way of achieving the same goal. Honestly, I think that the Red Hat’s hardening guide provides a “nicer” solution, but using chattr does the job too. You can use the method you prefer the most, I guess.

  4. Glad all my servers are under my own control so I don’t have to worry about too much of this. I harden the firewall, listening services, and whatever I host on it. And expect the OS to just work in a stable manner after that. KISS.

    • Half knowledge is dangerous, server hardening is an ongoing process, you almost always want to stay ahead of the game.

  5. Hi, seems that remove nullok from password-auth and system-auth is not enough. When authconfig is fired, these values are back.
    It’s necessary to remove it from system-auth-ac too/ Then authconfig doesn’t return nullok back to these files. Here is modified sed line:

    sudo sed -i ‘s/\//g’ /etc/pam.d/system-auth /etc/pam.d/password-auth /etc/pam.d/system-auth-ac

  6. Hi,

    you wrote: “Add the following line immediately before the pam_unix.so statement in the ACCOUNT section of /etc/pam.d/system-auth and /etc/pam.d/password-auth:” but in your example system-auth file it looks like this:
    account required pam_unix.so
    account required pam_faillock.so
    It’s after the unix.so line.

    But thanks for this guide it’s very helpfull for me.

  7. With the disabling of the USB ports script, does the system have some unique non changing identity value for each keyboard?

    Cause if you can check for this with a script couldn’t the usb auth scan for this value stored in either the script. To match it against a specified “authorized” keyboard identifier.

    Then based on its presence or not leave its connection port active (authorised) if present. If then continue to disable all USB ports.

    Any way thanks for sharing this great guide.

  8. Could this guide be used on a VPS server? I am using centos to run a website will any of the above cause any issues with that. I’m trying to lock down my server and I’m a novice.

    • I use it myself on AWS, so the short answer is yes.

      But please make sure that you understand what you’re doing before you apply any changes! Good luck.

    • Thanks for your reply. Yes doing it all on a test server first. Thank you again.

  9. With the new release of CentOS (7.8.2003) the hidepid=2 option on /proc generate the following polkit error when you try to manipulate services with systemctl command :
    ** (pkttyagent:1711): WARNING **: 15:16:57.009: Unable to register authentication agent: GDBus.Error:org.freedesktop.PolicyKit1.Error.Failed: Cannot determine user of subject
    Error registering authentication agent: GDBus.Error:org.freedesktop.PolicyKit1.Error.Failed: Cannot determine user of subject (polkit-error-quark, 0)
    I suppose this error is generate because the polkitd user cannot access to processes table.
    A resolution consist to authorize the polkitd user to see this system table by adding the gid option on /proc as follow :

    # groupadd nohideproc
    # usermod -a -G nohideproc polkitd
    # vi /etc/fstab

    proc /proc proc defaults,nosuid,nodev,noexec,hidepid=2,gid=nohideproc 0 0

    # init 6

    ;-)

    • Hi Rodrigo, thanks very much. The playbook looks good, really helpful. Just make sure you understand what it does before you run it :)

  10. Thanks for your very helpful article. When I try to make immutable system-auth file, it gives an error at below:
    chattr: Operation not supported while reading flags on /etc/pam.d/system-auth
    Is there any way to make immutable a symbolic link because system-auth is symbolic link ?
    Thanks in advance

Leave a Reply

Your email address will not be published. Required fields are marked *