Egress Filtering: Fencing in the Bad Guys
Carefully constructing walls and moats to keep the bad guys out of our systems is an everyday task. It's equally important to build walls and moats to keep them in, should they successfully penetrate our defenses.
Let's go ahead and review why filtering outbound traffic is so critical, as I still encounter decision makers who just don't get it. Consider Slammer and Code Red, for example, or Slapper and Scalper, or Distributed Denials of Service (DDoS). None of these would have had such a large impact if egress filtering were routinely implemented.
These are just the tip of the iceberg, though; as there's also spyware phoning home, backdoors, Trojans, attacks launched from your systems, employees running activities they shouldn't, and connection hijacking to contend with. And not all "bad" outgoing packets are the result of malice, as a misconfigured router might be lurking in a network. It's important to stop these from spewing wrong packets into the world, too.
The bottom line -- filtering outgoing traffic is just as important as filtering incoming.
What to Block and How to Block It
The idea is to permit only packets from trusted hosts to leave your network. Rule #1 never changes: turn off everything that is not needed. Mail servers don't need FTP or port 53, web servers don't need port 25, and so forth. This works at two levels: individual hosts and border routers. In other words, the goal is to ensure anything bad that escapes from a particular machine will get dropped at the border.
iptables is an extremely flexible tool that permits very fine-grained management of services, making it perfect for writing firewall rules. There's a bit of a learning curve to it, but it's well worth the effort to learn how to properly use this tool. Write iptables rules everywhere you can -- build a standalone firewall/router and use it on individual Linux hosts, for example. Depending solely on border defenses is simply not enough; layers of protection are good and iptables is free, so make the most of it.
Another alternative is the freeware edition of ZoneAlarm, which is an excellent tool for Windows clients, but you may need the commercial version to get sufficient functionality for your needs. In other words, while the free edition will work for most casual home users, business users will likely need the more fine-grained control of the Pro edition.
The following is not a complete firewall script -- don't blame me if the code samples provided below don't offer complete protection for your system. Rather than a netfilter howto, the following examples are simply useful and valuable iptables rules. Every system is different and has different needs, but keep in mind the overall concept -- "deny all; allow only as needed."
First, define the usual variables. Use what suits you; these sample values are listed so that the examples will make better sense:
Our default policy is DROP. A packet that matches none of the rules will be unceremoniously dropped. Routers should not disable forwarding, rather the workstations should not allow forwarding, unless there's a really good reason for enabling it.
$IPTABLES -P INPUT DROP
$IPTABLES -P OUTPUT DROP
$IPTABLES -P FORWARD DROP
We must enable loopback, or many things will break.
$IPTABLES -A INPUT -i lo -p all -j ACCEPT
$IPTABLES -A OUTPUT -o lo -p all -j ACCEPT
Log all dropped packets to syslog for later study. This is useful for refining rules and debugging. Logging is very flexible in iptables, as it is possible to create individual logs for each chain or protocol.
$IPTABLES -A INPUT -j LOG
$IPTABLES -A OUTPUT -j LOG
It's critical to prevent the big bad outside world from connecting to localhost. Note the use of REJECT rather than DROP. REJECT sends a response, whereas DROP does not -- it simply drops the packet cold, with no further processing or response. REJECT is better against certain types of attacks, such as port scanning, since it doesn't leave any dead sockets and tells the scanner to go away -- no one is home.
$IPTABLES -A INPUT -d lo -j REJECT--reject-with icmp-port-unreachable
There are many options for the type of response sent. Some others are:
$IPTABLES -A INPUT -d lo -j REJECT--reject-with icmp-network-unreachable
$IPTABLES -A INPUT -d lo -j REJECT--reject-with icmp-host-unreachable
IP spoofing is a favorite ploy of leet haxors (elite hackers) to hide their backtrail. Drop incoming packets that claim to originate from your host, and drop outgoing packets that do not. Many rulesets come in pairs. We're letting packets out on the assumption that our incoming packets have been examined and the bad guys blocked:
$IPTABLES -A INPUT -i $IFACE -s $LAN_IP -j DROP
$IPTABLES -A OUTPUT -o $IFACE -s ! $LAN_IP -j DROP
Restrict outgoing and incoming ICMP. Poor old ping has been put to many nefarious uses. In this example, we permit simple echo requests (ping) and simple echo replies (pong), both incoming and outgoing. Some admins like to completely block all ICMP requests. This usually is not a good idea, as many network functions and applications need ping to work properly. However, there's no hard-and-fast rule for this.
$IPTABLES -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
$IPTABLES -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
$IPTABLES -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
$IPTABLES -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
iptables does stateful packet filtering, so we can do things like the following, which allows established connections to send packets out (assuming, of course, that the inbound rules have done their job and not let anything malicious in):
$IPTABLES -I OUTPUT 1 -m state --state RELATED,ESTABLISHED -j ACCEPT
Access to Services
Here is a rule to allow DHCP. Use this only where it is necessary. For example, a network gateway with a static external IP won't need to talk to a DHCP server; however, your internal hosts may need to connect to an internal DHCP server. Note that ports are limited to UDP 67 and 68; TCP 67/68 are not allowed. Again, be very specific about what to allow:
$IPTABLES -A INPUT -p UDP -i $IFACE --dport 67 --sport 68 -j ACCEPT
Suppose you are not running your own mail server but must instead connect to an outside server:
$IPTABLES -A INPUT -p tcp --destination-port 25 \ -m state --state NEW,ESTABLISHED -j ACCEPT
To fine tune your rulesets, put sniffers on both sides of your firewalls or border routers and study the logs. Look at what hits your defenses and what escapes. Hopefully there will be no rude surprises, but if so, it is always better to know! This is where little old laptops that aren't much good for anything else are real handy -- set them up as portable network monitoring stations.
Again, I wish to emphasize this is not even close to being a complete firewall script. See the resources listed below for reference materials; the internet is also full of good tutorials and sample firewall scripts to study. If you're using some other means of packet filtering, the main concept of egress filtering still applies: don't let bad packets escape your network.
Linux Firewalls, Second Edition by Bob Ziegler
Building Secure Servers with Linux By Michael D. Bauer
Hacking Exposed Windows 2000 by Joel Scambray, Stuart McClure
The Netfilter/$IPTABLES Project