- Free Software Firewall Guide -
This section is designed to familiarize you with ipfilter's syntax, and firewall theory in general. The features discussed here are features you'll find in any good firewall package. This section will give you a good foundation to make reading and understanding the advanced section very easy. It must be emphasized that this section alone is not enough to build a good firewall, and that the advanced section really is required reading for anybody who wants to build an effective security system.
Config File Dynamics, Order and Precedence
IPF (IP Filter) has a config file (as opposed to say, running some command again and again for each new rule). The config file drips with Unix: There's one rule per line, the "#" mark denotes a comment, and you can have a rule and a comment on the same line. Extraneous whitespace is allowed, and is encouraged to keep the rules readable.
The rules are processed from top to bottom, each one appended after another.
This quite simply means that if the entirety of your config file is:
block in all
pass in all
The computer sees it as:
block in all
pass in all
Which is to say that when a packet comes in, the first thing IPF applies
is:
block in all
Should IPF deem it necessary to move on to the next rule, it would then
apply the second rule:
pass in all
At this point, you might want to ask yourself "would IPF move
on to the second rule?" If you're familiar with ipfwadm or ipfw, you probably
won't ask yourself this. Shortly after, you will become bewildered at the weird
way packets are always getting denied or passed when they shouldn't. Many packet
filters stop comparing packets to rulesets the moment the first match is made;
IPF is not one of them.
Unlike the other packet filters, IPF keeps a flag on whether or not it's going
to pass the packet. Unless you interrupt the flow, IPF will go through the entire
ruleset, making its decision on whether or not to pass or drop the packet based
on the last matching rule. The scene: IP Filter's on duty. It's been been scheduled
a slice of CPU time. It has a checkpoint clipboard that reads:
block in all
pass in all
A packet comes in the interface and it's time to go to work. It takes a
look at the packet, it takes a look at the first rule:
block in all
"So far I think I will block this packet" says IPF. It takes
a look at the second rule:
pass in all
"So far I think I will pass this packet" says IPF. It takes a
look at a third rule. There is no third rule, so it goes with what its last
motivation was, to pass the packet onward.
It's a good time to point out that even if the ruleset had been
block in all
block in all
block in all
block in all
pass in all
that the packet would still have gone through. There is no cumulative effect.
The last matching rule always takes precedence.
If you have experience with other packet filters, you may find this layout
to be confusing, and you may be speculating that there are problems with portability
with other filters and speed of rule matching. Imagine if you had 100 rules
and most of the applicable ones were the first 10. There would be a terrible
overhead for every packet coming in to go through 100 rules every time. Fortunately,
there is a simple keyword you can add to any rule that makes it take action
at that match. That keyword is quick.
Here's a modified copy of the original ruleset using the quick keyword:
block in quick all
pass in all
In this case, IPF looks at the first rule:
block in quick all
The packet matches and the search is over. The packet is expunged without
a peep. There are no notices, no logs, no memorial service. Cake will not be
served. So what about the next rule?
pass in all
This rule is never encountered. It could just as easily not be in
the config file at all. The sweeping match of all and the terminal
keyword quick from the previous rule make certain that no rules are
followed afterward.
Having half a config file laid to waste is rarely a desirable state. On the other hand, IPF is here to block packets and as configured, it's doing a very good job. Nonetheless, IPF is also here to let some packets through, so a change to the ruleset to make this possible is called for.
IPF will match packets on many criteria. The one that we most commonly think
of is the IP address. There are some blocks of address space from which we should
never get traffic. One such block is from the unroutable networks, 192.168.0.0/16
(/16 is the CIDR notation for a netmask. You may be more familiar with the dotted
decimal format, 255.255.0.0. IPF accepts both). If you wanted to block 192.168.0.0/16,
this is one way to do it:
block in quick from 192.168.0.0/16 to any
pass in all
Now we have a less stringent ruleset that actually does something for us.
Lets imagine a packet comes in from 1.2.3.4. The first rule is applied:
block in quick from 192.168.0.0/16 to any
The packet is from 1.2.3.4, not 192.168.*.*, so there is no match. The
second rule is applied:
pass in all
The packet from 1.2.3.4 is definitely a part of all, so the packet
is sent to whatever it's destination happened to be.
On the other hand, suppose we have a packet that comes in from 192.168.1.2.
The first rule is applied:
block in quick from 192.168.0.0/16 to any
There's a match, the packet is dropped, and that's the end. Again, it doesn't
move to the second rule because the first rule matches and contains the quick
keyword.
At this point you can build a fairly extensive set of definitive addresses
which are passed or blocked. Since we've already started blocking private address
space from entering our firewall, lets take care of the rest of it:
block in quick from 192.168.0.0/16 to any
block in quick from 172.16.0.0/12 to any
block in quick from 10.0.0.0/8 to any
pass in all
The first three address blocks are the unroutable IP space.
See rfc1918 at <http://www.faqs.org/rfcs/rfc1918.html>
It seems very frequent that companies have internal networks before they want
a link to the outside world. In fact, it's probably reasonable to say that's
the main reason people consider firewalls in the first place. The machine that
bridges the outside world to the inside world and vice versa is the router.
What separates the router from any other machine is simple: It has more than
one interface.
Every packet you recieve comes from a network interface; every packet you
transmit goes out a network interface. Say your machine has 3 interfaces, lo0
(loopback), xl0 (3com ethernet), and tun0 (FreeBSD's generic
tunnel interface that PPP uses), but you don't want packets coming in on the
tun0 interface?
block in quick on tun0 all
pass in all
In this case, the on keyword means that that data is coming in
on the named interface. If a packet comes in on tun0, the first rule
will block it. If a packet comes in on lo0 or in on xl0, the
first rule will not match, the second rule will, the packet will be passed.
Using IP Address and Interface Together
It's an odd state of affairs when one decides it best to have the tun0
interface up, but not allow any data to be recieved from it. The more criteria
the firewall matches against, the tighter (or looser) the firewall can become.
Maybe you want data from tun0, but not from 192.168.0.0/16? This is
the start of a powerful firewall.
block in quick on tun0 from 192.168.0.0/16 to any
pass in all
Compare this to our previous rule:
block in quick from 192.168.0.0/16 to any
pass in all
The old way, all traffic from 192.168.0.0/16, regardless of interface,
was completely blocked. The new way, using on tun0 means that it's
only blocked if it comes in on the tun0 interface. If a packet arrived
on the xl0 interface from 192.168.0.0/16, it would be passed.
At this point you can build a fairly extensive set of definitive addresses
which are passed or blocked. Since we've already started blocking private address
space from entering tun0, lets take care of the rest of it:
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
pass in all
You've already seen the first 3 blocks, but not the fourth. The
fourth is a largely wasted class-A network used for loopback. Much software
communicates with itself on 127.0.0.1 so blocking it from an external source
is a good idea.
There's a very important principle in packet filtering which has only been
alluded to with the private network blocking and that is this: When you know
there's certain types of data that only comes from certain places, you setup
the system to only allow that kind of data from those places. In the case of
the unroutable addresses, you know that nothing from 10.0.0.0/8 should be arriving
on tun0 because you have no way to reply to it. It's an illegitimate
packet. The same goes for the other unroutables as well as 127.0.0.0/8.
Many pieces of software do all their authentication based upon the packet's
originating IP address. When you have an internal network, say 20.20.20.0/24,
you know that the only traffic for that internal network is going to come off
the local ethernet. Should a packet from 20.20.20.0/24 arrive over a PPP dialup,
it's perfectly reasonable to drop it on the floor, or put it in a dark room
for interrogation. It should by no means be allowed to get to its final destination.
You can accomplish this particularly easily with what you already know of IPF.
The new ruleset would be:
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in quick on tun0 from 20.20.20.0/24 to any
pass in all
Bi-Directional Filtering; The "out" Keyword
Up until now, we've been passing or blocking inbound traffic. To clarify, inbound
traffic is all traffic that enters the firewall on any interface. Conversely,
outbound traffic is all traffic that leaves on any interface (whether locally
generated or simply passing through). This means that all packets coming in
are not only filtered as they enter the firewall, they're also filtered as they
exit. Thusfar there's been an implied pass out all that may or may
not be desirable. Just as you may pass and block incoming traffic, you
may do the same with outgoing traffic.
Now that we know there's a way to filter outbound packets just like inbound,
it's up to us to find a concievable use for such a thing. One possible use of
this idea is to keep spoofed packets from exiting your own network. Instead
of passing any traffic out the router, you could instead limit permitted traffic
to packets originating at 20.20.20.0/24. You might do it like this: [[footnote:
This can, of course, be changed by using -DIPFILTER_DEFAULT_BLOCK
when compiling ipfilter on your system. ]]
pass out quick on tun0 from 20.20.20.0/24 to any
block out quick on tun0 from any to any
If a packet comes from 20.20.20.1/32, it gets sent out by the first rule.
If a packet comes from 1.2.3.4/32 it gets blocked by the second.
You can also make similar rules for the unroutable addresses. If some machine
tries to route a packet through IPF with a destination in 192.168.0.0/16, why
not drop it? The worst that can happen is that you'll spare yourself some bandwidth:
block out quick on tun0 from any to 192.168.0.0/16
block out quick on tun0 from any to 172.16.0.0/12
block out quick on tun0 from any to 10.0.0.0/8
In the narrowest viewpoint, this doesn't enhance your security. It enhances
everybody else's security, and that's a nice thing to do. As another viewpoint,
one might suppose that because nobody can send spoofed packets from your site,
that your site has less value as a relay for crackers, and as such is less of
a target.
You'll likely find a number of uses for blocking outbound packets. One thing to always keep in mind is that in and out directions are in reference to your firewall, never any other machine.
Logging What Happens; The "log" Keyword
Up to this point, all blocked and passed packets have been silently blocked
and silently passed. Usually you want to know if you're being attacked rather
than wonder if that firewall is really buying you any added benefits. While
I wouldn't want to log every passed packet, and in some cases every blocked
packet, I would want to know about the blocked packets from 20.20.20.0/24. To
do this, we add the log keyword:
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 20.20.20.0/24 to any
pass in all
So far, our firewall is pretty good at blocking packets coming to it from
suspect places, but there's still more to be done. For one thing, we're accepting
packets destined anywhere. One thing we ought to do is make sure packets to
20.20.20.0/32 and 20.20.20.255/32 get dropped on the floor. To do otherwise
opens the internal network for a smurf attack. These two lines would prevent
our hypothetical network from being used as a smurf relay:
block in log quick on tun0 from any to 20.20.20.0/32
block in log quick on tun0 from any to 20.20.20.255/32
This brings our total ruleset to look something like this:
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 20.20.20.0/24 to any
block in log quick on tun0 from any to 20.20.20.0/32
block in log quick on tun0 from any to 20.20.20.255/32
pass in all
Complete Bi-Directional Filtering By Interface
So far we have only presented fragments of a complete ruleset. When you're
actually creating your ruleset, you should setup rules for every direction and
every interface. The default state of ipfilter is to pass packets. It is as
though there were an invisible rule at the beginning which states pass in
all and pass out all. Rather than rely on some default behaviour,
make everything as specific as possible, interface by interface, until every
base is covered.
First we'll start with the lo0 interface, which wants to run wild
and free. Since these are programs talking to other on the local system, go
ahead and keep it unrestricted:
pass out quick on lo0
pass in quick on lo0
Next, there's the xl0 interface. Later on we'll begin placing
restrictions on the xl0 interface, but to start with, we'll act as
though everything on our local network is trustworthy and give it much the same
treatment as lo0:
pass out quick on xl0
pass in quick on xl0
Finally, there's the tun0 interface, which we've been half-filtering
with up until now:
block out quick on tun0 from any to 192.168.0.0/16
block out quick on tun0 from any to 172.16.0.0/12
block out quick on tun0 from any to 10.0.0.0/8
pass out quick on tun0 from 20.20.20.0/24 to any
block out quick on tun0 from any to any
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 20.20.20.0/24 to any
block in log quick on tun0 from any to 20.20.20.0/32
block in log quick on tun0 from any to 20.20.20.255/32
pass in all
This is a pretty significant amount of filtering already, protecting 20.20.20.0/24
from being spoofed or being used for spoofing. Future examples will continue
to show one-sideness, but keep in mind that it's for brevity's sake, and when
setting up your own ruleset, adding rules for every direction and every interface
is necessary.
Controlling Specific Protocols; The "proto" Keyword
Denial of Service attacks are as rampant as buffer overflow exploits. Many
denial of service attacks rely on glitches in the OS's TCP/IP stack. Frequently,
this has come in the form of ICMP packets. Why not block them entirely?
block in log quick on tun0 proto icmp from any to any
Now any ICMP traffic coming in from tun0 will be logged and discarded.
Filtering ICMP with the "icmp-type" Keyword; Merging Rulesets
Of course, dropping all ICMP isn't really an ideal situation. Why not drop
all ICMP? Well, because it's useful to have partially enabled. So maybe you
want to keep some types of ICMP traffic and drop other kinds. If you want ping
and traceroute to work, you need to let in ICMP types 0 and 11. Strictly speaking,
this might not be a good idea, but if you need to weigh security against convenience,
IPF lets you do it.
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 0
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 11
Remember that ruleset order is important. Since we're doing everything
quick we must have our passes before our blocks,
so we really want the last three rules in this order:
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 0
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any
Adding these 3 rules to the anti-spoofing rules is a bit tricky. One error
might be to put the new ICMP rules at the beginning:
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 0
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 20.20.20.0/24 to any
block in log quick on tun0 from any to 20.20.20.0/32
block in log quick on tun0 from any to 20.20.20.255/32
pass in all
The problem with this is that an ICMP type 0 packet from 192.168.0.0/16
will get passed by the first rule, and never blocked by the fourth rule. Also,
since we quickly pass an ICMP ECHO_REPLY (type 0) to 20.20.20.0/24,
we've just opened ourselves back up to a nasty smurf attack and nullified those
last two block rules. Oops. To avoid this, we place the ICMP rules after the
anti-spoofing rules:
block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 20.20.20.0/24 to any
block in log quick on tun0 from any to 20.20.20.0/32
block in log quick on tun0 from any to 20.20.20.255/32
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 0
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any
pass in all
Because we block spoofed traffic before the ICMP rules are processed, a
spoofed packet never makes it to the ICMP ruleset. It's very important to keep
such situations in mind when merging rules.
TCP and UDP Ports; The "port" Keyword
Now that we've started blocking packets based on protocol, we can start blocking
packets based on specific aspects of each protocol. The most frequently used
of these aspects is the port number. Services such as rsh, rlogin, and telnet
are all very convenient to have, but also hideously insecure against network
sniffing and spoofing. One great compromise is to only allow the services to
run internally, then block them externally. This is easy to do because rlogin,
rsh, and telnet use specific TCP ports (513, 514, and 23 respectively). As such,
creating rules to block them is easy:
block in log quick on tun0 proto tcp from any to 20.20.20.0/24 port = 513
block in log quick on tun0 proto tcp from any to 20.20.20.0/24 port = 514
block in log quick on tun0 proto tcp from any to 20.20.20.0/24 port = 23
Make sure all 3 are before the pass in all and they'll be closed
off from the outside (leaving out spoofing for brevity's sake):
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 0
pass in quick on tun0 proto icmp from any to 20.20.20.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any
block in log quick on tun0 proto tcp from any to 20.20.20.0/24 port = 513
block in log quick on tun0 proto tcp from any to 20.20.20.0/24 port = 514
block in log quick on tun0 proto tcp from any to 20.20.20.0/24 port = 23
pass in all
You might also want to block 514/udp (syslog), 111/tcp & 111/udp (portmap),
515/tcp (lpd), 2049/tcp and 2049/udp (NFS), 6000/tcp (X11) and so on and so
forth. You can get a complete listing of the ports being listened to by using
netstat -a (or lsof -i, if you have it installed).
Blocking UDP instead of TCP only requires replacing proto tcp with
proto udp. The rule for syslog would be:
block in log quick on tun0 proto udp from any to 20.20.20.0/24 port = 514
IPF also has a shorthand way to write rules that apply to both proto
tcp and proto udp at the same time, such as portmap or NFS. The
rule for portmap would be:
block in log quick on tun0 proto tcp/udp from any to 20.20.20.0/24 port
= 111
| << Previous section: Introduction |