HOWTO - Limiting Access to TCP-wrapped Services with hosts.allow

Linh Pham

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.

Setting Up "hosts.allow"

File Syntax

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:

service
The name of the dæmon or service program that rule will be written for. Examples include popa3d, imapd, and sshd. You can also use the ALL wild card to cover all services and dæmons.
host/network
The host or network that the rule will apply to. You can use the any of the following notations to best suit your needs.
option
A command or an option telling it how to treat the connection request. In majority of the cases, you will see or use 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.

Options

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.

allow
As the name implies, it simply lets the connection through; this option must be at the end of a rule.
deny
Denies the connection without providing the requester any chance to try again. All denied connections are logged to syslog with the service and host that the deny rule matched.
spawn (command(s))
Generates a new process that will run the command(s) given, but the spawn commands themselves will not allow or deny the connection, so you must have either " : allow" or " : deny" at the end of the rule in order to make the rule truly effective.
twist command(s)
Like spawn, it will create a new process to allow you to run the necessary commands, with the common standard input, output and error objects are linked to the twist option. This option can be used to print out a customized message when someone tries to connect to a service like ftpd. Instead of allowing or denying the connection immediately, the twist commands determine if the requester should be let through or not. A twist option followed by only an echo command will send the echo'd text to the client and closes the connection.
rfc931 [timeout]
This option does a username lookup of the requester via ident, and checks to see if the username and the information povided by the requester match up. The timeout option is used to limit the amount of time that the machine will wait to receive a ident response.
nice [priority]
Like the 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.
banner /dir/path
This option searches for a file named after the specified service in /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

Tightening Down Your Services

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).

Words Of Caution

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.

Other Notes

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).

Conclusion

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.

Resources

Manual Pages


Errata

1. allow/deny ordering

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.