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