Security without firewalls
Gift-WrappedSecurity
Many years ago I remember somebody mentioning that rather than running a firewall, they were just using TCP Wrappers. This piqued my interest, because all my customers talked about when it came to Internet security was how much their proprietary firewall had cost them or which bundled features with their firewall guaranteed greater security for their servers.
Admittedly, the idea of totally dismissing firewalls goes against the grain – and more than just a little – however, you might be surprised to learn that I have successfully run several sets of production servers for many years with the absence of a firewall entirely. If you're wondering what I mean by "successfully," I mean without the servers' being compromised.
My brief addendum to those last two sentences is that running Netfilter [1] – or, to most peoples' minds, the tool that controls Netfilter, iptables – on a Linux server brings a great number of benefits, such as automatically dropping illegitimately formed traffic that might pose a threat to your applications or catching traffic to a port you forgot to close.
A word to the wise, therefore, is that if you fail to implement correctly the approach that I present in this article, iptables is the perfect hero to come to your rescue and make that tiny mistake less disastrous to your servers' security.
Firewalls Are Overrated
With a little planning and some consideration, you can safely connect Linux boxes to the Internet without anything but some Access Control Lists (ACLs) combined with an eye for minimalism. I'm referring to keeping the number of packages (and more specifically network services) to a minimum. By having, say, only three ports open on your server, such as HTTP, SMTP, and SSH, you're significantly limiting the number of attack vectors on your system.
Aside from network ports posing a threat, there's a rule of thumb that says for every thousand lines of code you add to a server, you potentially add another security hole. This is important because you can lock down all the network-facing ports but still make your applications vulnerable by adding screeds of unneeded code to your server.
Additionally, keeping packages at a minimum means not only that your backups are leaner (and thus easier to store because they'll use less storage space, and if they're smaller, they should also be quicker to restore in an emergency) but also that the code on your server is less vulnerable to attack. Another side effect is that fewer packages might mean less services that can accidentally open up a network port, which would otherwise add to your risk. Such a scenario might only be mitigated by running a comprehensive firewalling policy such as an efficacious iptables default-deny
.
Gift Wrap Your Security
I mentioned ACLs, and in this case, I'm talking about limiting access to services installed on your server, such as SSH, to specific IP addresses or restricting access with other rulesets. The open ports example I used was that of HTTP, SMTP, and SSH. It's highly unlikely that you would want to restrict all HTTP traffic to your server by IP address (e.g., unless the web server was on an intranet), but you might only want email from specific mail servers locked down by IP address. And, to my mind, almost every Internet-facing SSH server should be locked down by IP address. You can achieve this easily using a fantastic piece of software called TCP Wrappers, which uses tcpd
to log, check, restrict, and avoid spoofed network connections efficiently and in real time. TCP Wrappers also handles hostname lookups with ease, whether they're entered in a local database or a public DNS database.
TCP Wrappers has been around since the early days of the Internet and were written by a programmer named Wietse Venema, who is most famous for writing the truly excellent mail server software, Postfix [2]. The story goes that the talented Venema needed to keep track of attacks on workstations at a university and wrote a piece of software capable of limiting port access by rules.
Ignoring the use of a somewhat ancient network service, namely identd
, I'll focus on the use of IP addresses and names for configuring who is allowed to connect to ports.
How Do You Use It?
On many of the popular Linux distributions, two files live inside the /etc
directory: hosts.allow
and hosts.deny
. For the sake of simplicity, I'll start by denying all access to a server over the SSH service and then explicitly allow certain users access. It's similar to the deny by default approach I touched on in another article [3]. To achieve this, the /etc/hosts.deny
file would look like this:
sshd: ALL
To allow some IP addresses to connect /etc/hosts.allow
, the file would simply look like this:
sshd: 10.10.10.10, 1.2.3.4, 21.21.21.21
TCP Wrappers works nicely, even if you change the standard SSH port (it's usually TCP port 22) to port 2222, for example, to keep port scans from filling up your logs. Without TCP Wrappers enabled, scans might run dictionary attacks on your server where password combinations are guessed by one of many automated attack methods.
As well as being able to take individual IP addresses, hosts.allow
can happily handle the CIDR notation of classless IP address ranges, such as:
sshd: 10.10.10.0/24, \ 1.2.3.4/32, 21.21.21.0/19
IP addresses hosts.allow
can be set to accept connections from hostnames, too.
One example might be
ALL: *.domain.tld
or you might have an administrative group of machines that live under a set of hostnames:
ALL: *.admin.domain.tld
For clarity, even without the wildcard asterisk at the start of those examples, you can still allow any machine under that hostname umbrella, even if the line just starts with a period. Turning that example on its head entirely, ending with a period
ALL: 12.34.
would allow all machines with the first two octets in their IP address starting 12.34
, so, for example, 12.34.56.78
will be allowed to connect to ALL services and not just SSH. As well as these flexible options, you can also declare old school subnets directly:
sshd: 1.2.3.4/255.255.255.0
As well as the ALL
parameter, you can use LOCAL
, which allows any machine without a dot in its hostname. You could also edit your local hostname database (somewhat confusingly called /etc/hosts
) to declare that an oft-used computer called friend
had the IP address 10.10.10.10
. With the LOCAL
switch in your allow
file then, access to that service could be granted to all local machines with the following:
sshd: LOCAL
By accepting connections from any hostname without a dot, this might also cover any machine under the same domain name as the server you're working on.
The KNOWN
operator also exists, but I think it should be avoided, in case hostnames aren't served correctly when name servers are unavailable.
Stop
As you might have gathered, as your configuration files grow, there's a reasonable chance for making a typographical error. In many cases, this is harmless, but I once had a near miss over a dial-up connection into the back of a Linux router. Unfortunately, the console I was using wasn't set up properly, and I hit an extra carriage return by mistake on the sshd
line in hosts.allow
. This meant that all SSH access was broken until I spotted it.
To say that the order of the rules with TCP Wrappers is sensitive is a monumental understatement. So, if you decide to mix allow
and deny
rules in hosts.allow
(which I strongly do not recommend), then you should take a moment to check that your allow
rules are declared well in advance of your deny
rules. Otherwise, you might lose remote access.
Luckily, the modem I dialed into over the Linux router reset as expected within a minute or so. Then, I could dial back in to fix the broken hosts.allow
file and circumvent networking altogether before using SSH again. Not too many back door dial-up connections exist any more; however, I was lucky in some respects.
Mixing Up the Configuration Files
As I mentioned, I wouldn't recommend ignoring hosts.deny
and putting deny
configurations in your hosts.allow
file, but if you decided to do so for the sake of convenience, the syntax might look something like this:
sshd : localhost : allow sshd : 192.168. : allow sshd : .mydomain.tld : allow sshd : ALL : deny
I suspect this format harks back to older versions of TCP Wrappers.
Is Your Service Enabled?
Many Linux services almost surreptitiously enable TCP Wrapper usage by default. If you're unsure and the man page doesn't shed any light, then the following command should let you know if you can lock down the software using TCP Wrappers.
# ldd /path-to/software | grep libwrap.so
I've also seen libwrap0.so
work in some cases, so you can try that if the first command (just libwrap.so
) isn't successful. In many cases, you'll know you've hit the jackpot if any output comes back from that command at all.
Not So Static
You might be surprised that it's possible to go one step further with hosts.deny
and trigger commands when a nefarious connection is tracked by TCP Wrappers. In the two examples below, the trailing backslashes indicate continuation lines follow.
This first example means all banned connections are logged nicely to /var/log/tcp-wrappers.log
:
ALL : .example.com : spawn (/bin/echo \ %a from %h triggered an alarm %d >> \ /var/log/tcp-wrappers.log) : deny
Going one step further, the example in Listing 1 involves further action. Each time a running service port is probed, you can do far more than just log the attack details; instead, you could receive a nicely formatted email. Try it and see if you can add to its functionality.
Listing 1: Send Email on Attack
ALL: ALL: SPAWN ( echo "\n\TCP Wrappers\: Connection refused\n\By\: $(uname -n)\n\Process\: %d (pid %p)\n\User\:%u\n\Host\: %c\n\Date\: $(date)\n\" | /usr/bin/mail -s "Connection to %d blocked" chrisbinnie@email.com) &
The SPAWN command has lots of other uses, including communicating the attack to other servers, which would also instantly ban the attacking IP. Be warned, however, that generating email for every illegal service access could bring your mail server down (never mind that your server is being attacked), so use this script with some consideration to prevent creating your own specially crafted Denial of Service tool.
The End
Even though I only looked at TCP Wrappers very briefly, I hope you will agree that they are versatile, sophisticated, and yet surprisingly easy to use. There's little excuse for not using TCP Wrappers in some form for telnet access, and I'd be reluctant to deploy SSH on any server of value without them.
Finally, one outstanding feature is that the configuration files are "live," which means you don't have to stop and start services to refresh changes to the rulesets. This makes scripting around TCP Wrappers very simple and incredibly powerful. Popular packages such as Fail2ban [4] and PortSentry [5] have used them very effectively.