We explained earlier that connecting your system to a network significantly increases the risk of attack. With the common-sense considerations out of the way, it’s time to look more closely at basic network security. Here we’ll discuss a simple yet effective method of reducing the risk of unwanted network access, using a tool called TCP wrappers. This mechanism “wraps” an existing service (such as the mail server), screening the network connections that are made to it and refusing connections from unauthorized sites. This is a simple way of adding access control to services that weren’t originally designed for it, and is most commonly used in conjunction with the inetd or xinetd daemons.
TCP wrappers are somewhat equivalent to the security guards, or “bouncers,” that you might find protecting the entrance to large parties or nightclubs. When you approach a venue you first encounter the security guard, who may ask you your name and address. The guard then consults a guest list, and if you’re approved, the guard moves aside and allows you entry to the party.
When a network connection is made to a service protected by TCP wrappers, the wrapper is the first thing encountered. The wrapper checks the source of the network connection using the source hostname or address and consults a list that describes who is allowed access. If the source matches an entry on the list, the wrapper moves out of the way and allows the network connection access to the actual daemon program.
There are two ways to use TCP wrappers, depending on your Linux
distribution and configuration. If you are using the
inetd daemon for managing services (check to see
if the file /etc/inetd.conf
exists), TCP
wrappers are implemented using a special daemon called
tcpd. If you are using the
xinetd daemon instead (check for the directory
/etc/xinetd.d
), xinetd is
usually configured to use TCP wrappers directly.
We’ll describe each case in the following sections.
If your system uses the inetd daemon to launch
network services, it may be necessary to edit your
/etc/inetd.conf
file to use TCP wrappers.
Let’s use the finger daemon,
in.fingerd, as an example. The basic idea is that
instead of running the actual in.fingerd daemon,
inetd launches the tcpd daemon
instead. tcpd performs the TCP wrapper operation
and then runs in.fingerd in its place if the
connection is accepted.
Configuring TCP wrappers requires a very simple change to
/etc/inetd.conf
. For the finger daemon, you
might have an entry in this file, such as:
# /etc/in.fingerd finger daemon finger stream tcp nowait root /usr/sbin/in.fingerd in.fingerd
To protect the finger daemon using tcpd, simply
modify the /etc/inetd.conf
entry, as so:
# /etc/in.fingerd finger daemon finger stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.fingerd
Here we’ve caused the tcpd command to be executed instead of the actual in.fingerd command. The full pathname of the finger daemon is passed to tcpd as an argument, and tcpd uses this argument to launch the real daemon after it has confirmed that access should be allowed.
You’ll need to make this change for each daemon program you wish to protect. On most Linux systems you may find that tcpd is already configured, so these changes won’t be necessary.
xinetd is a replacement for
inetd that some distributions (such as Red Hat)
are adopting. In most cases, xinetd has built-in
support for TCP wrappers, so all you’ll need to do
is modify the TCP wrapper configuration files
(/etc/hosts.allow
and
/etc/hosts.deny
) as described in the next
section. If you are installing xinetd yourself, be
sure to compile support for TCP wrappers; this is described in the
xinetd documentation.
TCP wrappers use two configuration files,
/etc/hosts.allow
and
/etc/hosts.deny
. These files are used to specify
the access rules for each network daemon protected with TCP wrappers.
The files are described in detail in the
hosts_access manual page, but
we’ll cover the mechanics here because common cases
are fairly simple.
When a TCP wrapper is invoked, it obtains the IP
address of the connecting host and attempts to find its hostname
using a reverse DNS lookup. Next, it consults the
/etc/hosts.allow
file to see if this host is
specifically allowed access to the requested service. If a match is
found, access is allowed and the actual network daemon is invoked. If
no match is found in the /etc/hosts.allow
file,
/etc/hosts.deny
is consulted to see if this host
has been specifically denied access. If a match is found here, the
connection is closed. If no match is found in either file, access is
granted. This simple technique is powerful enough to cover most
access requirements.
The syntax of hosts.allow
and
hosts.deny
is fairly simple. Each file contains
a set of rules. Each rule is generally on one line but may be split
across multiple lines using a backslash at the end of the line. Each
rule has the general form:
daemon_list
:client_list
:shell_command
The daemon_list
is a comma-separated list of
daemons to which the rule applies. The daemons are specified using
their command basename; that is, the name of the actual executable
daemon program that is executed to fulfill the requested service. The
client_list
is a comma-separated list of hostnames
or IP addresses for which the rule will match.
We’ll demonstrate this later using an example. The
shell_command
is optional, and specifies a command
that will be executed when the rule matches. This can be used, for
example, to log incoming connections.
daemon_list
and client_list
may
contain patterns that allow you to match a number of daemons or hosts
without having to explicitly name each one. In addition, you can use
a number of predefined tokens to make rules simpler to read and
build. The patterns are quite sophisticated, so we
don’t cover them in detail here; instead we refer
you to the hosts_access manual page.
Let’s start with a simple
hosts.deny
file that looks like this:
# /etc/hosts.deny ALL: ALL
The first line is a comment. The next line is a rule that is
interpreted as follows: “Deny access requests to ALL
services from ALL hosts.” If our
/etc/hosts.allow
is empty, this rule will have
the effect of denying access to everything from all hosts on the
Internet — including the local host! To get around this problem,
we can make a simple change to the file:
# /etc/hosts.deny ALL: ALL EXCEPT localhost
This is nearly always a safe rule to have in place, as
it’s a secure default. Remember that the
/etc/hosts.allow
rules are consulted
before
/etc/hosts.deny
, so
by adding rules to hosts.allow
we can override
this default setting in hosts.deny
. For example,
imagine that we want to allow every host on the Internet to access
the finger daemon. To do this we add a rule to
/etc/hosts.allow
that looks like the following:
# /etc/hosts.allow in.fingerd: ALL
A more common use of TCP wrappers is to restrict the set of hosts
that can access a service. Hosts can be specified using
IP address, hostname, or some pattern based on the
address or hostname (e.g., to specify a group of hosts). For example,
consider making the finger daemon available only to a small set of
trusted hosts. In this case our hosts.allow
file
would be modified as follows:
# /etc/hosts.allow in.fingerd: spaghetti.vpasta.com, .vpizza.com, 192.168.1.
In this example we’ve chosen to allow FTP requests
from the host named spaghetti.vpasta.com, as well as from any
host in the vpizza.com domain,
and from any system with an IP address beginning with the pattern
192.168.1
.
The host and IP address matching rules in
hosts.allow
and hosts.deny
are important to understand, and the presence and location of the
period characters are critical. A pattern beginning with a period is
assumed to be the name of a domain to which requesting systems must
belong. A pattern ending with a period is assumed to specify an
IP address pattern. There are other ways of
specifying groups of hosts, including NIS
netgroups and explicit IP address netmasks. Full details on the
configuration syntax of these patterns is available in the
hosts_access manual page.