Skip to content

110.2 Setup host security

Weight: 3

Candidates should know how to set up a basic level of host security.

Objectives

  • Awareness of shadow passwords and how they work.
  • Turn off network services not in use.
  • Understand the role of TCP wrappers.

Terms

/etc/nologin, /etc/passwd, /etc/shadow, /etc/xinetd.d/, /etc/xinetd.conf, systemd.socket, /etc/inittab, /etc/init.d/, /etc/hosts.allow, /etc/hosts.deny

This objective is about four basic habits that make a Linux host safer:

  1. keep password hashes away from normal users with shadow passwords, and know how to block logins,
  2. understand super-servers (inetd, xinetd, systemd.socket) that listen for connections and start services on demand,
  3. find and turn off network services you do not need,
  4. use TCP wrappers as a simple host-based access list.

Shadow passwords

Every account has a line in /etc/passwd with seven fields. An easy way to remember the order is to follow a login: you type a name and a password, the system maps you to a user ID and a group ID, then comes the comment (GECOS), your home directory, and finally your shell:

emma:x:1000:1000:Emma Smith:/home/emma:/bin/bash
 |   |  |    |      |           |          |
name |  UID  GID  comment      home       shell
     password placeholder

/etc/passwd must be readable by everyone, because many programs use it to turn user IDs into names. So it is a bad place for passwords: anyone could copy the hashes and try to crack them offline. That is why the password field only holds an x. The x means "the real (hashed) password is in /etc/shadow", and that file is not readable by normal users:

$ ls -l /etc/passwd /etc/shadow
-rw-r--r-- 1 root root   2.5K Jun  5 19:14 /etc/passwd
-rw-r----- 1 root shadow 1.5K Jun 11 17:36 /etc/shadow
$ grep emma /etc/shadow
grep: /etc/shadow: Permission denied
$ grep nagato /etc/passwd
nagato:x:1000:1000:nagato:/home/nagato:/bin/bash
$ grep nagato /etc/shadow
grep: /etc/shadow: Permission denied

Normal users can still change their own password, because the passwd program runs with root rights (SUID) and only touches their own line.

Two commands change a user's entry in /etc/shadow: passwd sets the password and chage sets the aging and expiry values. As root:

$ sudo passwd emma
New password:
Retype new password:
passwd: password updated successfully
$ sudo chage -l emma
Last password change                                : Apr 27, 2020
Password expires                                    : never
Password inactive                                   : never
Account expires                                     : never
Minimum number of days between password change      : 0
Maximum number of days between password change      : 99999
Number of days of warning before password expires   : 7

Blocking logins

There are several ways to stop someone from logging in. Know all of them:

What you want How
expire one account chage -E 2020-03-26 emma (a date in the past)
lock one account for a while passwd -l emma
stop one account from getting a shell usermod -s /sbin/nologin emma
stop all users except root, for a while create the file /etc/nologin

After chage -E with an old date, the login fails:

$ sudo login emma
Password:
Your account has expired; please contact your system administrator

Authentication failure

/etc/nologin is the maintenance switch. If this file exists, nobody except root can log in, and the file's text is shown to anyone who tries. Delete it and logins work again:

# echo "System down for maintenance until 15:00" > /etc/nologin
# rm /etc/nologin                                                  # back to normal

/sbin/nologin is a dummy shell (usermod -s /sbin/nologin baduser). It politely refuses the login. It is the normal shell for service accounts, which must exist but should never be used to log in. The account stays active for other services such as mail or FTP. See man 5 nologin for the file and man 8 nologin for the shell.

Do not mix them up on the exam: /etc/nologin is a file that blocks everyone except root, /sbin/nologin is a shell that blocks one account.

Super-servers: inetd and xinetd

Normally each network service (web server, mail server, SSH) runs as a standalone daemon, always running and listening on its own port. Years ago, when machines had little memory, running many daemons side by side was expensive. So a super-server (also called a superdaemon or service dispatcher) listened on all those ports by itself and started the real service only when a connection arrived. The first connection is a little slower, but idle services use no resources. It also gives you one control point in front of the services, a bit like a wrapper.

                        +------------------+
  client --- port 22 -->|                  |--- starts on demand --> sshd
  client --- port 23 -->|  xinetd (always  |--- starts on demand --> in.telnetd
  client --- port 13 -->|     running)     |--- starts on demand --> daytime
                        +------------------+

The classic super-servers are inetd and its successor xinetd. Few systems use them today, but you must know their files.

The main file is /etc/xinetd.conf. It holds some defaults and one important line that includes a directory:

# Simple configuration file for xinetd
#
# Some defaults, and include /etc/xinetd.d/

defaults
{
# log_type = SYSLOG daemon info
}

includedir /etc/xinetd.d

Inside /etc/xinetd.d/ there is one file per service. The file name can be anything (except names containing a dot or ending with ~), but it is normal to name it after the service. Distributions ship templates for old services like daytime, echo or time, and all of them have disable = yes:

$ ls /etc/xinetd.d
chargen  chargen-udp  daytime  daytime-udp  discard  discard-udp
echo     echo-udp     servers  services     time     time-udp

Here is a file that lets xinetd handle SSH:

# /etc/xinetd.d/ssh
service ssh
{
    disable     = no
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = root
    server      = /usr/sbin/sshd
    server_args = -i
    flags       = IPv4
    interface   = 192.168.178.1
}
Directive Meaning
service the service name from /etc/services (like ssh) or a port number
disable no turns this service on, yes turns it off
socket_type stream for TCP, dgram for UDP
protocol tcp or udp
wait usually no for TCP: handle many connections at once
user the user the started service runs as
server full path of the program to start
server_args options for that program. Many services need a special option when started by a super-server, for sshd it is -i
flags for example IPv4 or IPv6
interface the address to listen on (bind means the same)

Other directives you may see: no_access = 10.0.1.0/24 (refuse these hosts), access_times = 09:45-16:15 (only allow during these hours), log_on_success and log_on_failure. A telnet example that uses them:

# /etc/xinetd.d/telnet
service telnet
{
    disable      = no         # yes turns this service off
    socket_type  = stream     # stream = TCP, dgram = UDP
    wait         = no         # no = handle many connections at once
    user         = root
    server       = /usr/sbin/in.telnetd
    no_access    = 10.0.1.0/24
    access_times = 09:45-16:15
}

To try it, stop the standalone SSH daemon, restart xinetd, and check who is listening on port 22 with lsof:

$ sudo systemctl stop sshd.service             # add "disable" to keep it off after reboot
$ sudo systemctl restart xinetd.service
$ sudo lsof -i :22
COMMAND   PID   USER  FD   TYPE  DEVICE   SIZE/OFF  NODE  NAME
xinetd    24098 root  5u   IPv4  7345141  0t0       TCP   192.168.178.1:ssh (LISTEN)

Port 22 now belongs to xinetd, not sshd. When a client connects, xinetd starts sshd -i to handle it. To turn a service off, set disable = yes and restart xinetd.

systemd socket units

On systemd systems, the job of xinetd is done by systemd.socket units. Some services, like ssh and cups, ship a .socket unit next to their .service unit. Stop the service, start the socket, and systemd itself listens on the port and starts the service when someone connects:

$ sudo systemctl stop ssh.service              # stop the always-on daemon
$ sudo systemctl start ssh.socket              # systemd now watches port 22 and starts ssh on demand (on some distributions: sshd.socket)
$ sudo lsof -i :22 -P                          # -P shows port numbers, not names
COMMAND  PID  USER  FD   TYPE  DEVICE    SIZE/OFF  NODE  NAME
systemd  1    root  57u  IPv6  14730112  0t0       TCP   *:22 (LISTEN)

Now PID 1 (systemd) owns port 22, and sshd only runs when needed.

Turning off unused services

Every running service is something an attacker can try to break, and it uses memory and CPU. The rule is simple: if you do not need it, turn it off. If the machine does not serve web pages, there is no reason to run Apache or nginx.

Which services are listening on the network? Use ss (or the older netstat from the net-tools package). -l shows only listening sockets, -t TCP and -u UDP:

$ ss -ltu
Netid  State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port
udp    UNCONN  0       0       0.0.0.0:bootpc      0.0.0.0:*
tcp    LISTEN  0       128     0.0.0.0:ssh         0.0.0.0:*
tcp    LISTEN  0       80      127.0.0.1:mysql     0.0.0.0:*
tcp    LISTEN  0       128     *:http              *:*
tcp    LISTEN  0       128     [::]:ssh            [::]:*

Here MySQL listens only on 127.0.0.1, so it is reachable from this machine only. SSH and HTTP are open to the network. netstat -ltu gives the same kind of list.

On systemd systems, list the running services, then stop and disable what you do not need. --now stops it right away, and disable keeps it from starting at the next boot:

$ systemctl list-units --state active --type service
$ sudo systemctl disable vsftpd.service --now   # stop now and at boot

On older SysV init systems, the start scripts live in /etc/init.d/, with links in the /etc/rcX.d/ (/etc/rc*.d/) directories for each runlevel. List all services with service --status-all (+ running, - stopped):

$ sudo service --status-all
 [ - ]  alsa-utils
 [ + ]  ufw
 [ + ]  vsftpd
 [ - ]  x11-common

Then remove a service from the boot process with the distribution's tool:

$ sudo update-rc.d vsftpd remove                # Debian based
$ sudo chkconfig vsftpd off                     # Red Hat based

/etc/inittab was the main configuration file of SysV init. Each line has the format id:runlevel:action:process, meaning "on these runlevels, do this action with this process":

1:2345:respawn:/sbin/mingetty tty1
id:3:initdefault:

The first line starts mingetty on tty1 in runlevels 2, 3, 4 and 5, and starts it again (respawn) if it dies. The second line is the most important one: it sets the default runlevel to 3. See 101.3 for runlevels.

TCP wrappers: /etc/hosts.allow and /etc/hosts.deny

Before Linux had firewalls, TCP wrappers were used to decide which hosts may connect to which service. Many programs no longer support them, and recent Red Hat based distributions (for example Fedora 29) removed them completely. You still need to understand them for the exam and for older systems.

Two files control access:

  • /etc/hosts.allow: hosts listed here are allowed to use the service,
  • /etc/hosts.deny: hosts listed here are denied.

The logic is like cron.allow and cron.deny: /etc/hosts.allow is checked first and is a whitelist, /etc/hosts.deny is a blacklist. A host that matches nothing is allowed. When a host matches both files, the allow entry wins. The format of each line is service: hosts, and ALL matches every service (or every host). LOCAL means hosts on the local network.

A service only reads these files if it is built with the libwrap library. Check with ldd:

$ ldd /usr/sbin/sshd | grep libwrap
    libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007f91dbec0000)
$ ldd /usr/sbin/vsftpd | grep libwrap
    libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0

A common pattern is "deny everyone, then allow a few". To make SSH reachable from the local network only:

# /etc/hosts.deny
sshd: ALL
# /etc/hosts.allow
sshd: LOCAL
# only 10.10.100.* may use FTP
vsftpd: 10.10.100.
# local connections may use anything
ALL: LOCAL

In these two files a # starts a comment only at the beginning of a line, so put comments on their own lines, never after a rule.

The changes take effect immediately, no service restart is needed. Test it by connecting with the ssh client from inside and outside the network.

Summary

Basic host security is a few habits I check on every machine. Shadow passwords keep the hashes out of the world-readable /etc/passwd (which only shows an x) and store them in /etc/shadow, which normal users cannot read, so users change their own password without seeing anyone else's; passwd and chage change that file. To block logins I expire an account with chage -E, lock it with passwd -l, give a service account the /sbin/nologin shell with usermod, or create the /etc/nologin file to keep everyone except root out during maintenance. Super-servers like inetd and xinetd (main file /etc/xinetd.conf, one file per service in /etc/xinetd.d/, switched off with disable = yes) once wrapped services, listening for connections and starting them on demand, and on systemd the same job is done by systemd.socket units such as ssh.socket. Above all, I turn off what I do not use: I find listening services with ss -ltu or netstat -ltu and lsof -i, then disable them with systemctl disable --now, or on SysV systems with update-rc.d (Debian) or chkconfig (Red Hat), remembering that old init scripts live in /etc/init.d/ and the default runlevel is set in /etc/inittab. Finally, TCP wrappers gate access by host through /etc/hosts.allow (whitelist, checked first) and /etc/hosts.deny (blacklist), only for services linked with libwrap, and changes apply right away.