- Free Software Firewall Guide -
This section doesn't necessarily teach you anything new about ipf, but it may raise an issue or two that you haven't yet thought up on your own, or tickle your brain in a way that you invent something interesting that we haven't thought of.
A long time ago at a university far, far away, Weitse Venema created the tcp-wrapper
package, and ever since, it's been used to add a layer of protection to network
services all over the world. This is good. But, tcp-wrappers have flaws. For
starters, they only protect TCP services, as the name suggests. Also, unless
you run your service from inetd, or you have specifically compiled it with libwrap
and the appropriate hooks, your service isn't protected. This leaves gigantic
holes in your host security. We can plug these up by using ipf on the local
host. For example, my laptop often gets plugged into or dialed into networks
that I don't specifically trust, and so, I use the following rule set:
pass in quick on lo0 all
pass out quick on lo0 all
block in log all
block out all
pass in quick proto tcp from any to any port = 113 flags S keep state
pass in quick proto tcp from any to any port = 22 flags S keep state
pass in quick proto tcp from any port = 20 to any port 39999 >< 45000
flags S keep state
pass out quick proto icmp from any to any keep state
pass out quick proto tcp/udp from any to any keep state keep frags
It's been like that for quite a while, and I haven't suffered any pain
or anguish as a result of having ipf loaded up all the time. If I wanted to
tighten it up more, I could switch to using the NAT ftp proxy and I could add
in some rules to prevent spoofing. But even as it stands now, this box is far
more restrictive about what it presents to the local network and beyond than
the typical host does. This is a good thing if you happen to run a machine that
allows a lot of users on it, and you want to make sure one of them doesn't happen
to start up a service they wern't supposed to. It won't stop a malicious hacker
with root access from adjusting your ipf rules and starting a service anyway,
but it will keep the "honest" folks honest, and your weird services
safe, cozy and warm even on a malicious LAN. A big win, in my opinion. Using
local host filtering in addition to a somewhat less-restrictive "main firewall"
machine can solve many performance issues as well as political nightmares
like "Why doesn't ICQ work?" and "Why can't I put a web server
on my own workstation! It's MY WORKSTATION!!" Another very big win. Who
says you can't have security and convienence at the same time?
What Firewall? Transparent filtering.
One major concern in setting up a firewall is the integrity of the firewall
itself. Can somebody break into your firewall, thereby subverting its ruleset?
This is a common problem administrators must face, particularly when they're
using firewall solutions on top of their Unix/NT machines. Some use it as an
arguement for blackbox hardware solutions, under the flawed notion that inherant
obscurity of their closed system increases their security. We have a better
way.
Many network admins are familiar with the common ethernet bridge. This is
a device that connects two separate ethernet segments to make them one. An ethernet
bridge is typically used to connect separate buildings, switch network speeds,
and extend maximum wire lengths. Hubs and switches are common bridges, sometimes
they're just 2 ported devices called repeaters. Recent versions of Linux, OpenBSD,
NetBSD, and FreeBSD include code to convert $1000 PCs into $10 bridges, too!
What all bridges tend to have in common is that though they sit in the middle
of a connection between two machines, the two machines don't know the bridge
is there. Enter ipfilter and OpenBSD.
Ethernet bridging takes place at Layer2 on the ISO stack. IP takes place on
Layer3. IP Filter in primarily concerned with Layer3, but dabbles in Layer2
by working with interfaces. By mixing IP filter with OpenBSD's bridge device,
we can create a firewall that is both invisible and unreachable. The system
needs no IP address, it doesn't even need to reveal its ethernet address. The
only telltale sign that the filter might be there is that latency is somewhat
higher than a piece of cat5 would normally make it, and that packets don't seem
to make it to their final destination.
The setup for this sort of ruleset is surprisingly simple, too. In OpenBSD,
the first bridge device is named bridge0. Say we have two ethernet
cards in our machine as well, xl0 and xl1. To turn this machine
into a bridge, all one need do is enter the following three commands:
brconfig bridge0 add xl0 add xl1 up
ifconfig xl0 up
ifconfig xl1 up
At ths point, all traffic ariving on xl0 is sent out xl1
and all traffic on xl1 is sent out xl0. You'll note that neither
interface has been assigned an IP address, nor do we need assign one. All things
considered, it's likely best we not add one at all.
Rulesets behave essentially the as the always have. Though there is a bridge0
interface, we don't filter based on it. Rules continue to be based upon the
particular interface we're using, making it important which network cable is
plugged into which network card in the back of the machine. Lets start with
some basic filtering to illistrate what's happened. Assume the network used
to look like this:
20.20.20.1 <---------------------------------> 20.20.20.0/24 network
hub
That is, we have a router at 20.20.20.1 connected to the 20.20.20.0/24
network. All packets from the 20.20.20.0/24 network go through 20.20.20.1 to
get to the outside world and vice versa. Now we add the Ipf Bridge:
20.20.20.1 <-------/xl0 IpfBridge xl1/-------> 20.20.20.0/24 network
hub
We also have the following ruleset loaded on the IpfBridge host:
pass in quick all
pass out quick all
With this ruleset loaded, the network is functionally identical. As far
as the 20.20.20.1 router is concerned, and as far as the 20.20.20.0/24 hosts
are concerned, the two network diagrams are identical. Now lets change the ruleset
some:
block in quick on xl0 proto icmp
pass in quick all
pass out quick all
Still, 20.20.20.1 and 20.20.20.0/24 think the network is identical, but
if 20.20.20.1 attempts to ping 20.20.20.2, it will never get a reply. What's
more, 20.20.20.2 won't even get the packet in the first place. IPfilter will
intercept the packet before it even gets to the other end of the virtual wire.
We can put a bridged filter anywhere. Using this method we can shrink the network
trust circle down an individual host level (given enough ethernet cards:-)
Blocking icmp from the world seems kind of silly, especially if you're a sysadmin
and like pinging the world, to traceroute, or to resize your MTU. Lets construct
a better ruleset and take advantage of the original key feature of ipf: stateful
inspection.
pass in quick on xl1 proto tcp keep state
pass in quick on xl1 proto udp keep state
pass in quick on xl1 proto icmp keep state
block in quick on xl0
In this situation, the 20.20.20.0/24 network (perhaps more aptly called
the xl1 network) can now reach the outside world, but the outside world
can't reach it, and it can't figure out why, either. The router is accessible,
the hosts are active, but the outside world just can't get in. Even if the router
itself were compromised, the firewall would still be active and successful.
So far, we've been filtering by interface and protocol only. Even though bridging
is concerned layer2, we can still discriminate based on IP address. Normally
we have a few services running, so our ruleset may look like this:
pass in quick on xl1 proto tcp keep state
pass in quick on xl1 proto udp keep state
pass in quick on xl1 proto icmp keep state
block in quick on xl1 # nuh-uh, we're only passing tcp/udp/icmp sir.
pass in quick on xl0 proto udp from any to 20.20.20.2/32 port=53 keep state
pass in quick on xl0 proto tcp from any to 20.20.20.2/32 port=53 flags S keep
state
pass in quick on xl0 proto tcp from any to 20.20.20.3/32 port=25 flags S keep
state
pass in quick on xl0 proto tcp from any to 20.20.20.7/32 port=80 flags S keep
state
block in quick on xl0
Now we have a network where 20.20.20.2 is a zone serving name server, 20.20.20.3
is an incoming mail server, and 20.20.20.7 is a web server.
Bridged IP Filter is not yet perfect, we must confess.
First, You'll note that all the rules are setup using the in direction
instead of a combination of in and out. This is because the
out direction is presently unimplimented with bridging in OpenBSD.
This was originally done to prevent vast performance drops using multiple interfaces.
Work has been done in speeding it up, but it remains unimplimented. If you really
want this feature, you might try your hand at working on the code or asking
the OpenBSD people how you can help.
Second, using IP Filter with bridging makes the use of IPF's NAT features inadvisable, if not downright dangerous. The first problem is that it would give away that there's a filtering bridge. The second problem would be that the bridge has no IP address to masquerade with, which will most assuredly lead to confusion and perhaps a kernel panic to boot. You can, of course, put an IP address on the outbound interface to make NAT work, but part of the glee of bridging is thus diminished.
Using Transparent Filtering to Fix Network Design Mistakes.
Many organizations started using IP well before they thought a firewall or
a subnet would be a good idea. Now they have class-C sized networks or larger
that include all their servers, their workstations, their routers, coffee makers,
everything. The horror! Renumbering with propper subnets, trust levels, filters,
and so are in both time consuming and expensive. The expense in hardware and
man hours alone is enough to make most organizations unwilling to really solve
the problem, not to mention the downtime involved. The typical problem network
looks like this:
20.20.20.1 router 20.20.20.6 unix server
20.20.20.2 unix server 20.20.20.7 nt workstation
20.20.20.3 unix server 20.20.20.8 nt server
20.20.20.4 win98 workstation 20.20.20.9 unix workstation
20.20.20.5 intelligent switch 20.20.20.10 win95 workstation
Only it's about 20 times larger and messier and frequently undocumented.
Ideally, you'd have all the trusting servers in one subnet, all the work- stations
in another, and the network switches in a third. Then the router would filter
packets between the subnets, giving the workstations limited access to the servers,
nothing access to the switches, and only the sysadmin's workstation access to
the coffee pot. I've never seen a class-C sized network with such coherancy.
IP Filter can help.
To start with, we're going to separate the router, the workstations, and the
servers. To do this we're going to need 2 hubs (or switches) which we probably
already have, and an IPF machine with 3 ethernet cards. We're going to put all
the servers on one hub and all the workstations on the other. Normally we'd
then connect the hubs to each other, then to the router. Instead, we're going
to plug the router into IPF's xl0 interface, the servers into IPF's
xl1 interface, and the workstations into IPF's xl2 interface.
Our network diagram looks something like this:
| 20.20.20.2 unix server
router (20.20.20.1) ____________| 20.20.20.3 unix server
| / | 20.20.20.6 unix server
| /xl1 | 20.20.20.7 nt server
------------/xl0 IPF Bridge <
xl2 | 20.20.20.4 win98 workstation
____________| 20.20.20.8 nt workstation
| 20.20.20.9 unix workstation
| 20.20.20.10 win95 workstation
Where once there was nothing but interconnecting wires, now there's a filtering
bridge that not a single host needs to be modified to take advantage of. Presumably
we've already enabled bridging so the network is behaving perfectly normally.
Further, we're starting off with a ruleset much like our last ruleset:
pass in quick on xl0 proto udp from any to 20.20.20.2/32 port=53 keep state
pass in quick on xl0 proto tcp from any to 20.20.20.2/32 port=53 flags S keep
state
pass in quick on xl0 proto tcp from any to 20.20.20.3/32 port=25 flags S keep
state
pass in quick on xl0 proto tcp from any to 20.20.20.7/32 port=80 flags S keep
state
block in quick on xl0
pass in quick on xl1 proto tcp keep state
pass in quick on xl1 proto udp keep state
pass in quick on xl1 proto icmp keep state
block in quick on xl1 # nuh-uh, we're only passing tcp/udp/icmp sir.
pass in quick on xl2 proto tcp keep state
pass in quick on xl2 proto udp keep state
pass in quick on xl2 proto icmp keep state
block in quick on xl2 # nuh-uh, we're only passing tcp/udp/icmp sir.
Once again, traffic coming from the router is restricted to DNS, SMTP,
and HTTP. At the moment, the servers and the workstations can exchange traffic
freely. Depending on what kind of organization you are, there might be something
about this network dynamic you don't like. Perhaps you don't want your workstations
getting access to your servers at all? Take the xl2 ruleset of:
pass in quick on xl2 proto tcp keep state
pass in quick on xl2 proto udp keep state
pass in quick on xl2 proto icmp keep state
block in quick on xl2 # nuh-uh, we're only passing tcp/udp/icmp sir.
And change it to:
block in quick on xl2 from any to 20.20.20.0/24
pass in quick on xl2 proto tcp keep state
pass in quick on xl2 proto udp keep state
pass in quick on xl2 proto icmp keep state
block in quick on xl2 # nuh-uh, we're only passing tcp/udp/icmp sir.
Perhaps you want them to just get to the servers to get and send their
mail with IMAP? Easily done:
pass in quick on xl2 proto tcp from any to 20.20.20.3/32 port=25
pass in quick on xl2 proto tcp from any to 20.20.20.3/32 port=143
block in quick on xl2 from any to 20.20.20.0/24
pass in quick on xl2 proto tcp keep state
pass in quick on xl2 proto udp keep state
pass in quick on xl2 proto icmp keep state
block in quick on xl2 # nuh-uh, we're only passing tcp/udp/icmp sir.
Now your workstations and servers are protected from the outside world,
and the servers are protected from your workstations.
Perhaps the opposite is true, maybe you want your workstations to be able
to get to the servers, but not the outside world. After all, the next generation
of exploits is breaking the clients, not the servers. In this case, you'd change
the xl2 rules to look more like this:
pass in quick on xl2 from any to 20.20.20.0/24
block in quick on xl2
Now the servers have free reign, but the clients can only connect to the
servers. We might want to batten down the hatches on the servers, too:
pass in quick on xl1 from any to 20.20.20.0/24
block in quick on xl1
With the combination of these two, the clients and servers can talk to
each other, but neither can access the outside world (though the outside world
can get to the few services from earlier). The whole ruleset would look something
like this:
pass in quick on xl0 proto udp from any to 20.20.20.2/32 port=53 keep state
pass in quick on xl0 proto tcp from any to 20.20.20.2/32 port=53 flags S keep
state
pass in quick on xl0 proto tcp from any to 20.20.20.3/32 port=25 flags S keep
state
pass in quick on xl0 proto tcp from any to 20.20.20.7/32 port=80 flags S keep
state
block in quick on xl0
pass in quick on xl1 from any to 20.20.20.0/24
block in quick on xl1
pass in quick on xl2 from any to 20.20.20.0/24
block in quick on xl2
So remember, when your network is a mess of twisty IP addresses and machine
classes, transparent filtered bridges can solve a problem that would otherwise
be lived with and perhaps someday exploited.
Drop-Safe Logging With dup-to and to.
Until now, we've been using the filter to drop packets. Instead of dropping
them, lets consider passing them on to another system that can do something
useful with this information beyond the logging we can perform with ipmon. Our
firewall system, be it a bridge or a router, can have as many interfaces as
we can cram into the system. We can use this information to create a "drop-safe"
for our packets. A good example of a use for this would be to implement an intrusion
detection network. For starters, it might be desirable to hide the presence
of our intrusion detection systems from our real network so that we can keep
them from being detected.
Before we get started, there are some operational characteristics that we need to make note of. If we are only going to deal with blocked packets, we can use either the to keyword or the fastroute keyword. (We'll cover the differences between these two later) If we're going to pass the packets like we normally would, we need to make a copy of the packet for our drop-safe log with the dup-to keyword.
If, for example, we wanted to send a copy of everything going out the xl3
interface off to our drop-safe network on ed0, we would use this rule
in our filter list:
pass out on xl3 dup-to ed0 from any to any
You might also have a need to send the packet directly to a specific IP
address on your drop-safe network instead of just making a copy of the packet
out there and hoping for the best. To do this, we modify our rule slightly:
pass out on xl3 dup-to ed0:192.168.254.2 from any to any
But be warned that this method will alter the copied packet's destination
address, and may thus destroy the usefulness of the log. For this reason, we
reccomend only using the known address method of logging when you can be certain
that the address that you're logging to corresponds in some way to what you're
logging for (e.g.: don't use "192.168.254.2" for logging for both
your web server and your mail server, since you'll have a hard time later trying
to figure out which system was the target of a specific set of packets.)
This technique can be used quite effectively if you treat an IP Address
on your drop-safe network in much the same way that you would treat a Multicast
Group on the real internet. (e.g.: "192.168.254.2" could be the
channel for your http traffic analysis system, "23.23.23.23" could
be your channel for telnet sessions, and so on.) You don't even need to actually
have this address set as an address or alias on any of your analysis systems.
Normally, your ipfilter machine would need to ARP for the new destination address
(using dup-to ed0:192.168.254.2 style, of course) but we can avoid
that issue by creating a static arp entry for this "channel"
on our ipfilter system.
In general, though, dup-to ed0 is all that is required to get a new copy of the packet over to our drop-safe network for logging and examination.
The dup-to method does have an immediate drawback, though. Since it
has to make a copy of the packet and optionally modify it for its new destination,
it's going to take a while to complete all this work and be ready to deal with
the next packet coming in to the ipfilter system.
If we don't care about passing the packet to its normal destination and we
were going to block it anyway, we can just use the to keyword to push
this packet past the normal routing table and force it to go out a different
interface than it would normally go out.
block in quick on xl0 to ed0 proto tcp from any to any port < 1024
we use block quick for to interface routing, because
like fastroute, the to interface code will generate two packet
paths through ipfilter when used with pass, and likely cause your system
to panic.
| << Previous section: Application |