This article originally appeared in the June 2002 issue of the Dæmon News Online Magazine. This is a cleaned-up version of the article with minor style edits and made it HTML5 compliant; else, the content has not been changed.
Note: Please note the errata at the end of this page, which includes any corrections made and/or additional notes added recently.
If you have a BSD server that allows access to POP3, IMAP4 or other services like ntalk and Samba's
SWAT tool, there are times that you wish that you could limit access to only a few workstations.
One way of limiting access to those services is to place the machine behind a firewall or some
port/packet filtering services, but that may not be a valid or efficient solution. Thankfully,
FreeBSD, OpenBSD, NetBSD and Mac OS X have a host access control facility configured via
/etc/hosts.allow
file. In this HOWTO, I will go over the basic syntax and concepts of
the file and some nuances that I have found. In this article, I will be using the default
hosts.allow
file from a recent build of FreeBSD 4.6-RC.
The syntax of /etc/hosts.allow
is actually quite simple and easy to understand, unlike,
say, sendmail.cf
or the scattered nature of qmail. Most of the rules and settings are
used in the following form:
service : host/network : option [: option] ...
where:
popa3d
, imapd
, and sshd
. You can also use the ALL
wild card to
cover all services and dæmons.10.4.2.5
or 172.16.7.4
10.4.
or 172.16.7.
10.4.0.0/255.255.0.0
or 172.16.7.0/255.255.255.0
[2f3f::]/10
or [4c20:fe37:1:1::]/64
foo.insecure.net
or bad.hairday.insecure.net
insecure.net
or hairday.insecure.net
foo
or bad
ALL
: All clients regardless of IP address or domain name.PARANOID
: Clients that have hostnames that don't match its
ident/domain lookup names. This does not apply to machines that do not have any reverse
domain lookup names.LOCAL
: A client that comes from the same machine or domain
as the host.UNKNOWN
: A client that cannot be resolved to anything known.KNOWN
: A client who's name and addresses can be resolved.allow
or deny
for the command. spawn
or twist
commands can also be used to send a customized error to the requester or send an alert to the administrator.
Some of the most commonly used options will be covered a bit later in this article.
The option field for each of the rules provides the basic allow
and deny
directives,
plus several other options that allow you to customize how it logs the connection attempt (via syslog
or e-mail), run a system command and other options that control the connection. Below are the most
common options that you may use in /etc/hosts.allow
and how they affect the connection.
command(s)
) : allow
" or
" : deny
" at the end of the rule in order to make the rule truly effective.command(s)
timeout
]priority
]nice
in BSD, this option allows you to change a process' priority; thus provides
means to limit how much processor a guest or a not-quite-so-trusting client can use./dir/path
/dir/path
, reads in
the contents, resolves expansions (a list of expansions can be found in hosts_access(5)
manual
page. This option is valid only for TCP services and dæmons, not for UDP services.
Comments in the /etc/hosts.allow
are very similar to shell scripts where each comment line starts
with a hash mark (#
) and extends to the end of the line. There is one significant difference in
how comments are handled in hosts.allow
and shell scripts is that comments are not allowed after
a rule. The following example shows valid uses of comments in the file.
# this is a valid comment portmap : 1.2.3.4 : deny # so is this sshd : 9.8.7.6 : deny # but not this one
Once you understand the syntax of setting up access controls and options, you are now ready to write
your own hosts.allow
file. Without the use of deny rules all running TCP-wrapped services or
enabled in inetd will be wide open until you explicitly. The example below allows shows some of the
possible ways to configure the hosts.allow
file.
portmap : localhost : allow portmap : 10. : allow portmap : .insecure.net : allow portmap : ALL : deny sshd : ALL : allow sshd : bad.host : deny sshd : 88.4.2. : deny (1) ALL : ALL : deny
Taking a look at the portmap section of the configuration, we are allowing localhost, any machines
with an IP address starting with 10.
and any machines that have .insecure.net
for
a domain name or ident name. All other machines and hosts are disallowed. If you do not include
localhost
in a rulset, you will lock the host out which may or may not be a good thing.
The sshd section states that all machines are allowed at first, but then blocks any machine with the
relative hostname of bad.host
or any machines with an IP address starting with 88.4.2
(1). The last rule in the configuration pretty does what it says,
which is to deny all machines access to the other "tcp wrapped" services and dæmons that do not
have any explicit rules stating otherwise. If you want to allow all hosts to the other services without
intervention, replace the last line with:
ALL : ALL : allow
If you do include a rule that denies all hosts access to a particular service and forget to include the
localhost or management hosts, you may end up inadvertantly lock yourself (and others) out. If you
setup a rule that allows all hosts to use a service and deny specific hosts, you may still leave yourself
open to other untrusted hosts. To limit such exposures, check /etc/inetd.conf
for any services
that are enabled but really do not need to be enabled, and disable them. One perfect example on
restricting access to services that are known to be inherently insecure is to only allow clients
from the internal network access to the telnet dæmon (telnetd).
No matter how tempting it is to setup a rule that says
ALL : ALL : deny
at the very top if the hosts.allow
file, DO NOT DO IT!. The access
control facility takes each rule literally, meaning that it will see that rule and deny the
connection before going through the rest of the rules. The same applies to denying access to all
hosts to a particular service first, then allowing the trusted hosts; the deny rule will be enacted
upon and the connection is rejected. For example, a bad way of writing a ruleset would be:
sshd : ALL : deny sshd : localhost : allow sshd : 10.0.3. : allow
Instead, re-write the ruleset to be:
ssh : 10.0.3. : allow ssh : localhost : allow ssh : ALL : deny
That way, the hosts that should be allowed will be allowed through, the rest will fail the first two rules and then finally gets denied when it reaches the deny rule.
If you write any rules that use domain or hostnames for the second field, you could be denying access to
hosts that do have a valid reverse domain lookup name (this includes IP addresses that do not have any
PTR records setup on the domain name server that is responsible for that subnet) or do not have any ident
dæmons running, if you do not include any allow rules for them before a ": ALL : deny
"
rule. Instead, you may want to allow specific subnets or IP address ranges to allow access to those services.
Any changes that are made to /etc/hosts.allow
take effect immediately (there is no need to restart
inetd or restart the network services). There are some options that will only work with TCP services or
cause problems when used with UDP services. Some of the caveats and warnings are documented in both
hosts_access(5)
and hosts_options(5)
manual pages. By using rules to allow or deny
connections, each connection made to the host requires some additional overhead to check the validity of the
client information and to compare that information with each of the applicable rules. Depending on the
average load on the host, number of connection attempted in a given time, and the available resources, using
hosts.allow
may not be a viable option.
Some services or dæmons provide more detailed facilities to allow or deny connections based on IP
address, domain name and other criteria that could be easier to configure than what is possible with
hosts.allow
. In addition, hosts.allow
cannot act as a filter for malicious code or
application level denial-of-service attacks (like the file-globbing exploit in earlier versions of ProFTPD
or a man-in-the-middle attack with ssh).
hosts.allow
is quite an interesting and useful facility that can help increase the security of the
host, but typos and poorly written rules can make the host as or more susceptable to exploits than without
those rules. Typos could also end up locking yourself out of the box when you least expect it. As stated
above, using the access control facility for "tcp wrapped" services and dæmons can and
probably will take a hit on the host's performance and possibly limit the response rate for the client.
The facility is not a complete security solution nor should it be treated as such, rather it's a
compliment to packet filtering and firewall solutions available (be it gratis, free-speech or commercial).
There are some features that are available only by using the hosts.allow
facility such as
redirecting specific clients to other services, or returning specific error messages, and banners
specific to the service and/or client. Nonetheless, this is a great facility that some people overlook as
a method to provide a simple means to deny access to specific servies to specific hosts.
Recently, a reader caught a mistake that was made in the article but was not caught before the article was originally published. In the version of the article that was published, the following section was written:
sshd : ALL : allow sshd : bad.host : deny sshd : 88.4.2. : deny
The sshd section states that all machines are allowed at first, but then blocks any machine with the
relative hostname of bad.host
or any machines with an IP address starting with 88.4.2
.
The correct block and description should be:
sshd : bad.host : deny sshd : 88.4.2. : deny sshd : ALL : allow
The sshd section states that any hosts with the hostname of bad.host
or machines that have an
IP address that stars with 88.4.2
are denied, then to allow anything else.
The reason why you do not want to start the file or a section with either:
service : ALL : allow
or
service : ALL : deny
(where service can be ALL
, sshd
or any other service) is that if that first
line matches, it will act on that line and anything below that will be ignored. So if you want to allow
access for one or all services to anyone except for a couple of hosts, you will want to add the rules to
block those hosts first, then add the allow
line.
The idea applies if you want to block access to one or all services to everyone except for one or more trusted hosts, you will want to allow those trusted hosts first, then deny everyone.
Article copyright © 2002–2010 Linh Pham. All rights reserved. Re-production of portions of this work, or its entirety, requires permission of the copyright holder.