Cover V13, i10

Article

oct2004.tar

Adding Intelligence to a Linux Firewall -- The Easy Way

Bill Stearns

Over the past 10 years, I've had the pleasure of watching the Linux firewall code grow up from Alan Cox's ipfw port, through ipfwadm and ipchains, and finally arrive at iptables. The earlier forms all eventually suffered from the fact that the filtering code was monolithic; you loaded one big kernel module that did it all. This approach made it difficult to add new features -- any new code could potentially affect the stability or performance of existing modules, so new features generally weren't added to ipfw, ipfwadm, or ipchains.

Paul "Rusty" Russell, the original designer of the iptables architecture, took a new approach. The different features needed to operate a firewall were broken out into generally independent kernel modules. For example, the code that provides the ability to inspect the length of a packet is stored in ipt_length.o, and the code that keeps track of open ftp sessions and their respective data ports is stored in ip_conntrack_ftp.o.

Here's an example. When a firewall ruleset needs to inspect the length of a packet in a given rule, the iptables command line first loads the necessary module with:

-m length
Now we can check whether the length of a packet is fewer than 68 bytes with:

-m length --length 0:67
There are a number of advantages to this modular approach. Loading only the modules needed for a specific firewall can save a little kernel memory, but more importantly can drastically reduce the amount of processing time needed for a given packet. If an administrator knows, for example, that the Amanda backup system is not used on her network, there's no need to load the connection tracking module that tracks of all open Amanda connections.

At first glance, this might seem to be a trivial improvement. In fact, this becomes a critical point for a firewall that handles large numbers of packets per second. In late 2003, Jack Zheng posted some throughput numbers for his router to the netfilter mailing list. With no netfilter modules loaded at all, that router was able to handle 580,000 packets/second. By putting in netfilter firewall rules, but not enabling connection tracking, that number dropped to 450,000 packets/second. When both the core netfilter modules and connection tracking were turned on, the number dropped again to 295,000 packets/second. That's by no means shabby, but it's half the system ceiling.

The numbers you'll see will vary drastically according to the hardware and kernel in use. The point is that you can reduce the amount of processing per packet for core routers that need to operate at the highest possible packet rates and load more security modules on firewalls closer to the machines they're protecting.

Another advantage of this modular approach is that it allows new functionality to be added not only without major changes to the existing kernel code but also, in many cases, without even rebooting! If I want to be able to inspect the payload of packets for a given string of characters, the stock kernels don't provide that functionality. However, I can compile the ipt_string.o module against the current kernel source, load that module, and then I will have the ability to look for "Classified" in character streams leaving the Web server as one additional layer in my security.

There's a collection of additional firewall code that can be added to your existing kernel in the "patch-o-matic-ng" collection found at:

ftp://ftp.netfilter.org/pub/patch-o-matic-ng/
If you'd like to try some of the additional modules, pull down the latest snapshot and look at the README file, which gives a short description of the process. The "runme" script will present each of the additional kernel modules and ask if you'd like to use each. Once it's done patching your kernel tree, you'll need to recompile the kernel or at least the new modules.

Writing Rules

There's one critical piece of firewall construction that the modular firewall approach does not address; the complexity of writing firewall rules. This has always been a laborious process, and one that unfortunately assumes that you have a strong background in packet filtering, TCP/IP networking, and shell scripting. To be sure, there are graphical tools available on freshmeat.net or from a Google search that will help you write firewall rules, but those walk a fine line between being simple enough to make the process easier than writing the command lines yourself and being comprehensive enough to provide access to the advanced features available in the all these additional iptables modules.

The Modwall project (http://www.stearns.org/modwall/) was started a few years back as a way of providing firewall building blocks -- short sections of a firewall rule set that an administrator can insert into an existing firewall, improving security with an absolute minimum of effort. Modwall doesn't try to write your core "allow this protocol from here to here" rules, but rather focuses on malicious and malformed traffic. In other words, it inserts rules into your firewall that block packets that have no legitimate reason to exist, cleaning up the data stream and allowing you to focus on the remainder with your existing iptables ruleset.

Here's a simple example. As you may already know, not all of the IPV4 address space from 0.0.0.0 to 255.255.255.255 has been assigned to ISPs and corporations for use by their customers; a significant portion of that space is unallocated. The unallocated space is called the "bogon" address space. Any packet that crosses a network wire with a bogon address in either the source or destination address field is almost certainly malicious (the only exceptions being if you use those addresses internally). Any packet arriving at my system from 2.3.4.12 should be discarded as the entire 2.0.0.0/8 range is unallocated.

The list of unallocated addresses -- the "bogon list" -- can be found at:

http://www.cymru.com/Documents/bogon-bn-agg.txt
This particular file lists those domains in CIDR notation. The Web site also has this data in other forms.

Ideally, you as an administrator could run a command like:

bogons DROP
inside your core firewall script, and that command would insert the firewall rules that discard all packets with either a source or destination bogon address. And, it's really that easy; the Modwall package includes a shell script called "bogons" that creates the rules to do exactly that. When run, the script creates these firewall rules:

/sbin/iptables -N bogons
/sbin/iptables -A bogons -s 0.0.0.0/7 -j DROP
/sbin/iptables -A bogons -d 0.0.0.0/7 -j DROP
/sbin/iptables -A bogons -s 2.0.0.0/8 -j DROP
/sbin/iptables -A bogons -d 2.0.0.0/8 -j DROP
/sbin/iptables -A bogons -s 5.0.0.0/8 -j DROP
/sbin/iptables -A bogons -d 5.0.0.0/8 -j DROP
[snipped for brevity]
/sbin/iptables -A INPUT -i ! lo -m state --state NEW,RELATED -j bogons
/sbin/iptables -A FORWARD -m state --state NEW,RELATED -j bogons
/sbin/iptables -A OUTPUT -o ! lo -m state --state NEW,RELATED -j bogons
There are a few things to note here. All of the Modwall scripts store their respective rules in a user-defined chain -- in this case, the aptly named "bogons" chain. Organizing the rules this way has a couple of advantages.

First, you're not left with a huge ruleset with all the rules in the INPUT, OUTPUT, and FORWARD chains, so readability and maintainability improve.

Second, you can make changes to individual chains without affecting the rest of the firewall. Say you wanted not only to drop the packets but also log them to syslog. No problem; simply run:

bogons LOG DROP replace
and the rules in that chain are replaced with a new set that will log each packet before dropping it. The "replace" operation builds the new ruleset, atomically replaces the old "bogons" chain with the new one, and removes the old chain. That leaves no gaps where neither the old nor new ruleset is working, which would be the case if the entire firewall had to be restarted with each little change.

A common trait among firewall administrators and other security types is paranoia (or, perhaps that's just me). Regardless, I wanted to provide ways for you to test the Modwall scripts without blindly having to trust me. That's a good thing, as your network may have facets I hadn't anticipated -- "You're really using IP version 14 on the mail server?"

The first way to get a sense of what the rules will do is to add the "--dry-run" option to the command line, as in:

bogons --dry-run | less
With this, you get an exact list of the commands that module will run. That's how I got the listing of bogon rules above. If you're reasonably happy with the way the rules look, but you'd like to try them in a running firewall without actually acting on the packets, run:

bogons NONE insert
The "NONE" option puts in firewall rules that take no action on the packet whatsoever; after traversing the bogons chain they continue down into the rest of the firewall and are handled just as they always would have been. By putting these dummy rules in place, you still get counts of how many packets match these rules. You can later run:

iptables -L bogons -nxv
to get a listing of the rules in the bogons chain and to show how many packets (first column) and bytes (second column) have been matched by those rules and hence would have been dropped, or logged and dropped, etc. by those rules.

By the way, the "insert" option only affects where the bogon chain is called. In the first example of the article, the calls from INPUT, OUTPUT, and FORWARD were all "-A", or "APPEND" rules. Inside your main firewall script this would do exactly what you'd want in building a firewall ruleset from beginning to end. In this example, however, we're working with a live firewall, so we want to give the bogons chain a chance to inspect and drop packets before they would be accepted by later rules such as "allow all port 80 traffic to the Web server". For that reason, we use "insert" to stick the call to bogons at the top of the INPUT, OUTPUT, and FORWARD chains.

The Modwall collection includes 38 other firewall modules. Each one has a "help" command-line option that shows a quick summary of the available options, a list of what kernel modules are required to run correctly, and a short description of what the brick does and under what circumstances it should be considered safe to use. Running:

icmpchk help | less
gives the command-line summary and this short description of the "icmpchk" module: "The icmpchk module puts in some blocks for fragmented icmp packets (illegal) and address mask and timestamp requests and replies. At best, these are uncommon and are used in network mapping. These rules should be safe to use on any network."

With this background and the ability to look at the actual rules produced if you want more detail, you can now make an intelligent choice about whether you'd like to use these modules in your firewall.

Other Modwall Modules

The bulk of the Modwall scripts focus on identifying packets with malicious characteristics (there are a few with specialized uses). For example, the tcpchk script looks for illegal tcp packets. TCP packets have 6 primary flags: URG, ACK, PSH, RST, SYN, and FIN. There are 64 possible ways that those flags can be turned on or off in a given packet, and most of those are illegal combinations that would never show up in a legitimate TCP conversation.

Tcpchk puts in rules that identify (and DROP, by default, although as with every module you can choose what action to take on malicious packets) the packets with illegal flag combinations. It also pays attention to the state of the connection that this packet is in. For example, a packet with "SYN" alone set would be perfectly legal in a "NEW" iptables connection but illegal as an "ESTABLISHED" or "RELATED" packet. tcpchk also blocks packets going to the low ports (0 to 19) as these are rarely used legitimately.

The packet length module ("plength") gets deep down into the details of small packets. The Internet RFCs set rules for how short packets can legally be. plength uses those RFCs, some basic logic about the different protocols, and how large both fragmented and non-fragmented packets need to be to provide minimum length rules. Packets that are shorter than these minimum lengths are being artificially fragmented, perhaps by a tool like fragroute that chops packets up into pieces small enough to slip by some intrusion detection systems.

The rules in plength are a perfect example of why it makes sense to have one person figure out how to perform a check and share that knowledge rather than asking each administrator to write the iptables rules from scratch. Let's look at an example.

RFC791 says that every IPv4-capable router needs to be able to forward packets of 68 bytes or less without fragmentation (the number comes from the maximum IP header size of 60 bytes and the fact that we need to carry at least one 8-byte chunk of the payload). By this logic, any fragment of a packet (except the last) smaller than 68 bytes is being artificially chopped into smaller bits, perhaps to sneak a malicious payload past an IDS.

The plength module puts in this rule to discard unnecessarily small fragments:

/sbin/iptables -A plength -m u32 --u32 \
  "3&0x20>>5=1" -m length --length 0:67 -j DROP
In this example, we're using two iptables kernel modules, u32 and length. The length check is pretty straightforward -- the "-m length --length 0:67" returns true if the total length of the packet is between 0 and 67 bytes. The other kernel module needs a bit of explanation.

The u32 iptables Module

If you glance through the collection of iptables modules, you'll see that you can test most of the fields in the ip, tcp, udp, and icmp headers. That's not universally true, however. For example, there's no direct way to test the "More Fragments" flag.

U32 was written to perform tests not available in other iptables kernel modules. The firewall administrator can use it to:

  • extract 32 bits from some location in the packet
  • apply a bitmask to them, and
  • compare the result to a value or range.

Let's look at a simpler example before we dissect the plength example from above

I'd like to perform a simplistic test against the destination IP address. I'd like to see whether the last octet of the address is 255, and we'll consider that a broadcast packet.

The core syntax for u32 is:

-m u32 --u32 "Start&Mask=Range"
In the command line, we supply the values for Start, Mask, and Range; the "&" and "=" go in verbatim.

Since the u32 module pulls out 4-byte blocks of the IP header by default, we give u32 a Start address of byte 16 of the IP header to extract the destination address that lies in bytes 16, 17, 18, and 19. We only really want the last byte, however, so we apply a mask of 0x000000FF. We want to compare that value to 255 to do the broadcast test. The test looks like:

-m u32 --u32 "16&0xFF=255"
When added to an iptables command line, this returns true if the last octet of the destination address is 255, false otherwise.

Let's revisit that plength test above. The goal is to determine whether the "More Fragments" flag is set. Since that's stored in byte 6 of the IP header, we'll tell u32 to grab bytes 3, 4, 5, and 6. Our starting address is 3.

Next, we'll apply a mask to throw away everything except the one bit in which we're interested -- bit 5 of the last byte. In binary, that's:

00000000 00000000 00000000 00100000
Its hex equivalent is 0x00000020, or just 0x20.

The bit we want to test is still at bit position 5; we'll move it down to the lowest position with the "right shift" operator in the u32 mini-grammar. This is done by adding ">>5".

Finally, we want to test that value to see if it's a 1, so our Range value is just a "1". The final test is:

-m u32 --u32 "3&0x20>>5=1"
If that seems a bit tough to grasp at first glance, you're right. Using this module takes some thought. However, this and the other iptables modules live up the Unix philosophy of providing simple, efficient, and flexible building blocks that can be glued together to make something more powerful.

References

http://www.netfilter.org -- The home Web site for the Netfilter/Iptables Linux firewall project.

http://www.stearns.org/modwall/ -- The home site for the modwall Modular Firewall project.

http://www.stearns.org/doc/iptables-u32.current.html -- This provides some more detailed explanation of the u32 module syntax and a large number of annotated examples.

http://www.sans.org/resources/tcpip.pdf -- SANS provides a tri-fold printable flyer with the most commonly used header diagrams.

Bill Stearns is a faculty member and author for the SANS Institute. His current projects include spam filtering, firewall construction, and User-Mode Linux. His Web site at http://www.stearns.org/ contains a number of administrative tools and articles. He and his wife, Debra, live in New Hampshire, USA.