Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 17 Jun 1999 17:35:36 -0600 (MDT)
From:      Brendan Conoboy <synk@swcp.com>
To:        freebsd-security@freebsd.org
Subject:   ipf howto, tada
Message-ID:  <199906172335.RAA00665@kitsune.swcp.com>

next in thread | raw e-mail | index | archive | help
Hi Everybody.  I know it's been a while since I said I'd put this
out, but I'm easily distracted and prone to switching character.
Here's my alpha-release ipf-howto.  It's somewhere between a
firewall howto and an ipf howto.  Think of it is a guide to
setting up an ipf-based firewall.  It has some theory, some
caveats, some typos, bad spelling, poor grammar, anthropomorphism,
person shifts, so and so forth.  If Dr Seuss were here, I would
implore him to invent 5 letters before alpha to describe it.  In
any case, here it is.  Please send me lots of questions, comments,
additions, retractions, and offers to be an editor.  Thanks,

-Brendan (synk@swcp.com)

-----------------------------------------------------------------------

IP Filter is a neat little firewall package.  It does just about
everything other free firewalls (ipfwadm, ipchains, ipfw) do, but
it's also portable and does neat stuff the others don't.  This
document is intended to make some cohesive sense of the sparse
documentation presently available for ipfilter.  Some prior
familiarity with packet filtering will be useful (too much familiarity
may make this document a waste of your time).  There's probably
numerous books on the subject that would be beneficial to read.  I
used TCP/IP illustrated, myself.  On we go.

Part 1 - 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.  Extranious
whitespace is allowed, I use it to keep my rules readable.


The rules are read 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 it, it'll 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 gateway 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:

"So far I think I will pass this packet" says IPF.  It takes
a look at a third rule.  There is no third rule (a null pointer
is dereferenced, signal 12, kernel panic- AIiiyyee!), 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 still would have gone through.  There is no cumulative
effect.  The last matching rule always takes precedence.

I'm sure there's a good use for this method of layout, but I must confess
that I don't yet grasp it.  It has some horrible aspects in the area of
portability and speed.  Imagine converting your old filters to this format,
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's
a simple keyword you can add to any rule that makes it take action
at that match.  The 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 matchs 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 heck of a 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 numerous criteria.  The one that we most commonly
think of is the IP address.  There are some blocks of address from which we
should never get traffic.  One such block are the unroutable networks such
as 192.168.0.0/16 (/16 being a netmask.  Perhaps you are more familiar with
the wordier version of 255.255.0.0).  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 wherever it happened to be going.

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.

Every packet you recieve comes from a network interface.
>From a modem speaking PPP to ethernet cards talking 10 base-T,
there's an interface involved.  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
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 xl0, the first
rule will not match, the second rule will, the packet will be passed.

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

A packet from 192.168.1.1 on the xl0 interface will get passed.
A packet from 1.2.3.4 on the tun0 interface will get passed.
Infact, every packet from everywhere will be allowed in, except
for packets from 192.160.0.0/16 on tun0.

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

The first three address blocks are the unroutable IP space.  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 external
entrance is a good idea.

It seems very frequent that companies have internal networks before
they want a link to the outside world.  I'd even go so far as to
say that's the main reason people consider firewalls in the first
place, though they're just as important for a single machine as
for an office network.  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 two interfaces.
We'll call them tun0 for the external link and xl0 for the internal
link.

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 200.200.200.0/24, you know that the only traffic for that internal
network is going to come off the local ethernet.  Should a packet from
200.200.200.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 200.200.200.0/24 to any
pass  in       all

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 200.200.200.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 200.200.200.0/24 to any
pass  in       all

[a month passes while I become fascinated with thingamajigs, gadgets
 and other non-ipf related gadgets]

There's a couple other things you may need to do, too.  If you're
running FreeBSD, you'll need to have IPFILTER_LOG compiled into
your kernel.  Second, ipf requires ipmon to log, no matter what OS
you're using.  I prefer to run ipmon with as "ipmon -s" so it
syslogs logged packets instead of having them dump to stdout.

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 packes to 200.200.200.0/32 and 200.200.200.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 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.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 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass  in       all

Denial of Service attacks are almost as rampant as buffer overflows.
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.
Of course, this isn't really an ideal situation.  Why not drop all ICMP?
Well, because it's useful.  So maybe you want to keep some types of ICMP
traffic.  If you want ping and traceroute to work, you need to let in
icmp types 8 and 11.  Strictly speaking, this might not be a good idea,
but if you need to weigh security verses convenience, ipf lets you do it.

pass in quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 8
pass in quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 11

Remeber 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 200.200.200.0/24 icmp-type 8
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any

Now to put it all together:

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 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 8
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any
pass  in       all

Please note the location of the icmp rules.  They're intentionally placed
behind the first 7 blocking rules because we don't want to accidentally
pass some icmp traffic that would have been dropped by the anti-spoofing
and anti-smurfing rules.  It's very important to keep a close eye on what
you "pass" before all the relevent "block" rules are run.

Now that we've started blocking packets based on protocol, we can start
blocking packets based on protocol ports.  Services such as rsh, rlogin,
and telnet are all very convenient to have.  They're also hidiously
insecure against network sniffing and spoofing.  One great compromise is
to only allow the services to run internally, then block them externally.
Both rlogin, rsh, and telnet use specific TCP ports (513, 514, and 23 
respectively).  Creating rules to block them is easy:

block in     quick on tun0 proto tcp from any to 200.200.200.0/24 port = 513
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 port = 514
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 port = 23

Make sure all 3 are before the "pass in all" and those ports are good to
not go:

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 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 8
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 icmp-type 11
block in log quick on tun0 proto icmp from any to any
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 port = 513
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 port = 514
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 port = 23
pass  in       all

You might also want to block udp/111 (portmap), tcp/515 (lpd), tcp/udp
(2049), tcp/6000 (X) and so on and so forth.  You can get a complete
listing of the ports being listed to by using "netstat -a" or lsof, if
you have it installed.  Blocking udp is the same as tcp.  The rule for
portmap would be:

block in     quick on tun0 proto udp from any to 200.200.200.0/24 port = 111

There's a big problem with blocking services by the port: sometimes they
move.  RPC based programs are terrible about this, lockd, statd, even
nfsd listens places other than 2049.  It's awfully hard to predict, and
even worse to automate adjusting all the time.  What if you miss a service?
Instead of dealing with all that hassle, lets start over with a clean
slate.  The current ruleset looks like this:



Yes, we really are starting over.  The first rule we're going to use is
this:

block in quick

No network traffic gets through. None. Not a peep.  You're rather
secure with this setup.  Not terribly useful, but quite secure.  The
great thing is that it doesn't take much more to make your box rather
secure, yet useful too.  Lets say the machine this is running on is
a web server, nothing more, nothing less.  It doesn't even do DNS
lookups.  It just wants to take connections on tcp/80 and that's it.
We can do that.  We can do that with a eecond rule, and you already
know how:

pass  in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80
block in quick on ed0

This machine will pass in port 80 traffic for 200.200.200.1, and deny
everything else.  Perhaps this is all one needs?

Well, I usually find myself needing more.  I want to be able to telnet
out over the ed0 interface.  I want convenience and security in one.
Lots of people seem to, that's why ciscos have an "established" clause
that lets established tcp sessions go through.  Ipfw has established.  
Ipfwadm has setup/established.  They all have this feature, but the
name is very misleading.

When I first saw it, I thought it meant my packet filter was keeping
track of what was going on, that it knew if a connection was really
established or not.  The fact is, they're all taking the packet's word
for it.  That's why they only support established TCP connections,
that's the only protocol that has flags which the router can
extrapolate the established state of the connection.  Anybody who can
create a packet with bogus flags can get by such a firewall.  

Where does IPF come in to play here, you ask?  Well, unlike the other
firewalls, ipf really can keep track of whether or not a connection
is established.  And it'll do it with udp and icmp, not just tcp.  The
only problem is this: the way it does it is non-intuitive.  Ipf calls
it "keeping state".  The keyword for the ruleset is "keep state" (There
is also "keep frags", which is advisable as well).

Keeping state's setup is weird.  Normally, we know that when we want a
packet to come in, we used "pass in" and when we want one coming in to
be blocked we use "block in.".  State is different in that, if you want
a packet to come in, you say "pass out".  By passing the packet out,
a reciprical rule is created that allows a reply to come back in the
other direction.  Lets show this as an actual rule:

pass out quick proto tcp from 200.200.200.1/32 to any keep state

Thought it doesn't say so, you must imagine there being an auxillery
rule that exists in the same place in line that reads:

pass in quick proto tco from ThatIpISentTo to 200.200.20.1/32

There is no way to express the previous line, don't even try putting
it in there, it won't work.  The "keep state" option makes it implicit.

Here's what our ruleset looks like now:

pass out quick in ed0 proto tcp from 200.200.200.1/32 to any keep state
pass  in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80
block in quick on ed0

The workings of the keep state ruleset is much like the workings of the
saying "do not speak until spoken to."  It's just not permitted (except
on port 80:-).  Actually, lets keep state on udp and icmp packets as
well:

pass out quick in ed0 proto tcp/udp from 200.200.200.1/32 to any keep state
pass out quick in ed0 proto icmp    from 200.200.200.1/32 to any keep state
pass  in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80
block in quick on ed0

Ipf has a tcp/udp shorthand.  It's actually two rules, but you can
write it as one.  Now we're keeping state on tcp, udp, icmp.  That
means we can make outgoing connections with ease.  Nothing is denied
to us, but the outside world can only send packets to our port 80 with
any sort of freedom.

Lets take a look at what happens, rule by rule, if I ping to www.3com.com:

First, a DNS packet is generated, because I don't know the IP address of
www.whitehouse.gov.  DNS uses UDP, so the first rule matches, the packet
is passed, and ipf lets my nameserver's IP reply back to me (since I
spoke to it before it spoke to me).  It told me www.3com.com is at
192.156.136.22.  Now I send an icmp packet to 192.156.136.22.  The second
rule matches this since it's an icmp packet.  The packet goes out, an
exception is made for a reply to come back, the reply comes back, the
exception is removed.  This is very handy because there's no need to
track down what ports we're listening to, only the ports we want people
to be able to get to.

For added scrutiny, we might consider changing

pass  in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80

to:

pass in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80 flags S ke
ep frags

This will only let packets through to port 80 if they have a Syn flag.  This
is handy in detecting Fin scans (where the Fin flag is set).

So now we have a pretty tight firewall:


But it can still be tighter.  Some of the original ruleset we wiped clean
is actually very useful.  I'd suggest bringing back all the anti-spoofing
stuff.  This leaves us with:


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 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass out quick in ed0 proto tcp/udp from 200.200.200.1/32 to any keep state
pass out quick in ed0 proto icmp    from 200.200.200.1/32 to any keep state
pass in  quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80 flags S k
eep frags
block in quick on ed0

Now we're looking pretty good.

------------------------------------------------------------------------------
Copyright 1999 Brendan Conoboy (synk@swcp.com)



To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-security" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199906172335.RAA00665>