Cover V09, I06
Article

jun2000.tar


FreeBSD Firewall Tools & Techniques

Michael Lucas

FreeBSD includes a variety of security tools. These tools can be used to build an inexpensive, reliable firewall for your organization. The basic security measures I will cover are:

• Host hardening

• IP transaction control

• Application proxies

• Post-install maintenance

The specific components you choose will depend on your security policy. Each tool controls traffic in a different way, and may or may not fit your needs. This information can also be applied to NetBSD and OpenBSD, with some minor changes.

OpenBSD is a secure-by-default version of BSD. OpenBSD also incorporates some bleeding-edge security tools, however, such as encrypting swap space on the fly. If you're working on an OpenBSD system, your system is already hardened and you can skip that portion. Any BSD variant can be made secure by a competent systems administrator, but since FreeBSD is in wider use, I will focus on that.

If you need a better grounding in the basics of FreeBSD, either see the FreeBSD Handbook (http://www.FreeBSD.org/handbook/) or my article “FreeBSD for the SysV/Linux Administrator” (Sys Admin, March 2000).

Who Should Build a Firewall?

Building a firewall from source code isn't something to be taken lightly. You must have a good working knowledge of TCP/IP, a good understanding of your firewall's operating system, and have current knowledge of Internet security. An excellent resource is Practical UNIX & Internet Security, by Simon Garfinkel and Gene Spafford (O'Reilly and Associates).

Host Design and Hardening

The firewall host itself must be the most secure machine on your network. All your systems can be compromised if the firewall is successfully attacked. Even if an attack only crashes the firewall without compromising information, your organization will suffer. Start with high-quality components. A $400 PC might suffice for a user who doesn't mind a low mean time between failures, but this is unacceptable for a point-of-failure system.

You don't need a large or fast disk, unless you're doing extensive logging. If you are logging thoroughly, you probably want to set up a separate logging host so you can write some simple CGI scripts to generate the Web-based reports that management loves. While you probably won't need a RAID array, you will need a good backup in case of a hard-disk crash. If you're an experienced BSD administrator, you might consider a flash disk.

The firewall should be installed in a secure location, with tightly controlled physical access. The best proxies and filters won't do you a bit of good if the firewall host itself is compromised. Hardening the OS secures it against attack. Hardening the operating system is useful on all hosts on the exposed Internet, not just firewalls. Start with the network services. On FreeBSD 3.2 and above, you can use sockstat to find which network daemons are running.

% sockstat
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS  FOREIGN ADDRESS
daemon   portmap    392    3 udp    *.111                 *.*
daemon   portmap    392    4 tcp    *.111                 *.*
root     inetd      155    4 tcp    *.21                  *.*
root     inetd      155    7 tcp    *.23                  *.*
root     inetd      155    8 tcp    *.514                 *.*
root     inetd      155    9 tcp    *.513                 *.*
root     inetd      155   10 tcp    *.79                  *.*
root     inetd      155   11 udp    *.512                 *.*
root     inetd      155   12 udp    *.518                 *.*
%
This provides a list of all open network services, their associated program, their PID, file descriptors, the address they're listening on, and the address of any remote connection. Each daemon listening on a socket is a potential attack point. On a firewall, the sockets must all be closed.

In a default FreeBSD installation, the biggest source of listening daemons is inetd. portmap, syslogd, and Sendmail are also open by default. You can disable all of these with the following settings in /etc/rc.conf:

inetd_enable="NO"
syslogd_flags="-ss"
portmap_enable="NO"
sendmail_enable="NO"
The changes will take effect the next time you reboot. Run sockstat again and check for listening services. You should see no sockets open. Now that you have a network-secure machine, you can open a few carefully chosen proxies of your choice.

On a non-firewall machine, you can secure inetd, portmap, Sendmail, and most other daemons through tcp_wrappers (default in 3.x and above). Similarly, syslogd's -a flag allows you to specify which hosts may connect to the syslogd socket.

You can also harden your system at the filesystem layer. Once your firewall is complete, you can edit /etc/fstab to mount / and /usr read-only. Similarly, you can mount /var and /tmp noexec, so anything on them cannot be executed. A typical /etc/fstab might look like:

# Device         Mountpoint   FStype  Options        Dump    Pass#
/dev/wd0s1b      none         swap    sw              0       0
/dev/wd0s1a      /            ufs     rw              1       1
/dev/wd0s1f      /usr         ufs     rw              2       2
/dev/wd0s1e      /var         ufs     rw              2       2
/dev/wcd0c       /cdrom       cd9660  ro,noauto       0       0
proc             /proc        procfs  rw              0       0
/dev/wd0s1b      /tmp         mfs     rw              0       0
/dev/fd0         /mnt         ufs     rw,nodev,noauto 0       0
To mount a filesystem read-only, change the rw option to ro. To mount it noexec, add ,noexec after rw under options.

Mounting /usr read-only isn't appropriate in some situations, especially while a firewall is going through its initial testing. The main purpose behind mounting a filesystem read-only is to prevent file alteration, and you'll probably need to alter files repeatedly as you tune the system. Effects similar to a read-only system can be achieved with file flags.

The flags we need to worry most about are schg (system immutable), sappnd (system append-only), and sunlnk (system undeletable). See man (1) chflags for a complete list of file flags. A file marked schg cannot be changed, even by the superuser. Similarly, a sappnd file cannot be truncated, and a sunlnk file cannot be deleted. You should protect any critical binaries and log files on a read-write filesystem with the appropriate flags.

You set a system flag with:

% chflags schg <filename>
You can set an entire directory hierarchy immutable with the -R option. On your production firewall, you might want to immute the entire /bin, /sbin, /usr/bin, /usr/sbin, /lib, and /usr/lib directories, and parts of others.

For a flag to take effect, the system must be running in “secure mode.” FreeBSD has a default securelevel of -1, meaning that none of the filesystem security controls are irreversible. On your firewall host, you will want to use at least securelevel 1, and probably securelevel 3 when the system is completely tuned.

Securelevels have the following effects at each level:

-1 Permanently insecure mode -- Any security changes made can be overridden at the command line while the system is in multi-user mode.

0 Insecure mode -- Immutable and append-only flags can be turned off, and all devices may be read or written to as their permissions allow.

1 Secure mode -- Immutable and append-only flags cannot be turned off, and disks for mounted filesystems, /dev/mem, and /dev/kmem cannot be opened for writing.

2 Highly secure mode -- As securelevel 1, plus unmounted disks cannot be opened for writing (except by mount(2)).

3 Network secure mode -- As securelevel 2, plus IPFW, IPFilter, and dummynet configurations cannot be altered.

Use sysctl to set a running system's securelevel:

% sysctl -w kern.securelevel=3
To set securelevel at boot time, do the following /etc/rc.conf:

kern_securelevel="3"
Securelevel can be raised on a running machine, but cannot be lowered without dropping to single-user mode. If you're using IPFilter to control traffic, you don't want to set the securelevel in /etc/rc.conf. I'll show later where to do that.

Remember, newsyslog cannot rotate append-only logfiles; you must drop to single-user mode to perform that maintenance. If you are logging large amounts of network traffic, you're better off logging to a separate host that can rotate its logs more easily. The best defense your organization can have is regularly examined logs. Despite all this, however, your system might still be compromised. A tool like tripwire (/usr/ports/security/tripwire) will help you detect when this happens. Tripwire has been documented in extensive detail elsewhere, so I won't cover it here.

IP Transaction Control

One of the core functions of a firewall is to control who can make what sorts of connections through it. FreeBSD includes two sets of software for this purpose: IPFW and IPFilter. IPFW is a classic FreeBSD utility, and is available on all open-source BSD platforms. IPFilter provides stateless packet filtering, and has been tested and improved for several years now.

IPFilter is a newer tool that performs stateful inspection of all packets. While IPFilter is included with FreeBSD, it is maintained as a separate project. The version of IPFilter in FreeBSD might or might not be the very latest. You can find full details on IPFilter at http://coombs.anu.edu.au/~avalon.

Stateful inspection was recently integrated into IPFW. This is only available on FreeBSD 4.x, however. Many people are still on FreeBSD 3.x (or even the unsupported 2.x), so I'll use IPFilter in my examples. IPFW has a similar configuration, and many people use it very effectively: see man (8) ipfw for details on use.

A stateless filter simply looks at each individual packet. If the rules allow that type of packet, it is passed. The drawback with stateless filters is that it is possible to spoof IPs that appear to be part of an existing connection. The firewall doesn't track existing connections, and doesn't realize that the connection was never requested.

Stateful inspection maintains a table of existing connections. If a packet arrives claiming to be part of an established connection, but no corresponding connection appears in the connection state table, the packet is dropped. Stateful inspection provides more thorough control of your connections than a stateless filter.

Kernel Configuration

For IPFilter, add the following to your kernel.

options         IPFILTER          #kernel ipfilter support
You might also want:

options         IPFILTER_LOG      #ipfilter logging
options         IPFILTER_LKM      #kernel support for ip_fil.o LKM
The IPFILTER_LKM option is experimental. For a production firewall, you probably don't want to use it. Since the machine will be providing continuous protection, you don't want to be able to unload the firewall module.

Filter Configuration

IPFilter uses a very powerful descriptive language for its rules. For a complete description, see the file /usr/src/contrib/ipfilter/BNF. This contains many things you'll probably never use, but the basic syntax is very simple. Let's look at an example:

block in log quick from any to any with ipopts
The format is simple: block or pass, in or out, options, from where, to where, and other options. The most common options are log, quick, proto, and on. If you want to log the packet, use the log option. The quick option aborts all further rule processing. In our example rule, if a packet has IP options we don't need to know any more; we reject it. The proto option allows you to choose which network protocols the rule applies to, either by number or name as shown in /etc/protocols.

The on option allows you to specify an interface. If you wanted to stop packets with IP options on the ed0 interface, you could specify it like:

block in log quick on ed0 from any to any with ipopts
The from and to keywords are the source and destination address of the packet. You can follow them with a few more options that will control ICMP packets, stateful inspection, and other packet details I'll cover later.

The script mkfilters will use your machine's current configuration to build a set of initial IPFilter rules. These rules will prevent basic spoofing attacks, remote attacks to loopback addresses, absurdly short packets, and similarly well-known chicanery. If mkfilters isn't in your $PATH, look under /usr/src/contrib/ipfilter. You will also find the IPFilter source code, original documentation, and examples here.

My example host has an ISDN link to the outside world, and an Ethernet card on the inside. tun0 is the outside link, and ed0 is the inside. I have one outside IP address; for the purpose of this example, let's say it's 209.69.80.8. My inside NIC has an IP of 192.168.66.1.

The mkfilter script gives me the following:

% mkfilters > /etc/ipf.conf
% cat /etc/ipf.conf
#
# The following routes should be configured, if not already:
#
# route add 192.168.66.1 localhost 0
#
block in log quick from any to any with ipopts
block in log quick proto tcp from any to any with short
pass out on ed0 all head 150
block out from 127.0.0.0/8 to any group 150
block out from any to 127.0.0.0/8 group 150
block out from any to 192.168.66.1/32 group 150
pass in on ed0 all head 100
block in from 127.0.0.0/8 to any group 100
block in from 192.168.66.1/32 to any group 100
block in from 209.69.80.8/0xffffff00 to any group 100
pass out on tun0 all head 350
block out from 127.0.0.0/8 to any group 350
block out from any to 127.0.0.0/8 group 350
block out from any to 209.69.80.8/32 group 350
pass in on tun0 all head 300
block in from 127.0.0.0/8 to any group 300
block in from 209.69.80.8/32 to any group 300
block in from 192.168.66.1/0xffffff00 to any group 300
%
Rules can be arranged in groups. This improves performance; rather than checking every packet against every rule, you can follow different paths through the rules based on packet characteristics. The fewer rules a packet must go through before being approved or rejected, the faster your firewall will run. mkfilters makes a simple group structure, based on the direction a packet traverses an interface.

In this example, group 100 controls access from the inside network to the firewall and outside network. Group 300 controls access from the outside world to the firewall and inside. Group 150 controls access from the firewall to the inside network, and group 350 controls access from the firewall out. All packets are checked against every rule without a “group” or “head” statement.

“Head” statements are the beginning of rule groups. If a packet matches a “head” statement, it is checked against all members of that group. If the packet doesn't match the head statement, it isn't checked against members of that group. For example, take a packet coming in on tun0. Like all packets, it will be checked against the rules:

block in log quick from any to any with ipopts
block in log quick proto tcp from any to any with short
The packet will hit this rule:

pass in on tun0 all head 300
and switch into group 300. It will not be affected by rules in any other group.

block in from 127.0.0.0/8 to any group 300
block in from 192.168.1.200/32 to any group 300
These rules stop packets from loopback addresses, as well as packets from outside that appear to have an address on the local machine. Note that they no longer include on tun0; the head rule covers that. These rules should be used even if you have a permissive firewall; they block things that can only result from misconfiguration or malice. This configuration is useful even on a non-firewall, single-interface host on the exposed Internet.

Now that you have a basic setup, you can begin restricting traffic. If you have a restrictive security policy, you'll want to add a final rule like this:

block from any to any
This will stop any traffic coming into your system. To allow access, you open holes with particular rules. Start with access from the inside network to the outside world. We'll start by assuming no local proxies; redirection will be covered later.

First, find the port number of all services you want to offer. Check /etc/services if you're not sure. Let's assume you want to offer outbound HTTP and pop3 service. To keep our rules short, branch new connections into a new group:

block in log proto tcp all flags S/SA head 101 group 100
Any packets with the SYN flag set will be blocked and diverted into group 101. (The SYN flag is set to signal the beginning of a new TCP connection.) This rule catches all requests for new connections. Then, we can follow up with:

pass in quick proto tcp from any to any port \
   = www keep state 
  group 201

pass in quick proto tcp from any to any port \ = pop3 keep state group 201

The “keep state” keyword adds in the rules necessary for return packets to be accepted. You don't need to add any rules on the exterior interface to support these outgoing connections. When the packet hits the “quick” option, it is immediately passed on. Adding new protocols to your access list, both inbound and outbound, is this simple.

Many organizations aren't interested in controlling outbound traffic, but want to control who may connect in. In this case, you can use rules like:

pass in quick proto tcp from 192.168.66.0/24 \
   to any keep state 
  group 201
pass in quick proto udp from 192.168.66.0/24 \
   to any keep state 
  group 201
If you want to strictly control who may access an outside service, you can specify internal IP addresses in a rule:

pass in quick proto tcp from 192.168.66.12 \
   to any port = www keep 
  state group 201
Once your configuration file is set, you can start IPFilter with the command:

% ipf -f /etc/ipf.conf
% ipf -F o && ipf -f /etc/ipf.conf
It's also possible to feed rules into your running configuration at the command line. If you're building your first firewall, however, you will want to be certain that your configuration file matches the settings that actually work for you. See man (1) ipf for details. Set up rules to allow incoming connections in the same way. Your internal network is probably on non-routable addresses, however, so you'll also have to specify redirection in your address translation.

Address Translation

Most organizations don't have enough routable IP addresses to assign one to every host on their internal network. IPFilter includes a network address translation utility, ipnat. The ipnat configuration for our example above is very simple:

map tun0 192.168.66.0/24 -> 209.69.80.8/32 \
   portmap tcp/udp 10000:60000
map tun0 192.168.66.0/24 -> 209.69.80.8/32
The syntax is very simple: the map keyword, the inside IP address range, and the outside IP address range. The portmap keyword allows the firewall to change source ports for connection requests. Without this, if two inside hosts were to make a connection request from the same port, the NAT would become confused. The 10000:60000 is the range of port numbers to be used; 50,000 is generally enough for most organizations' outgoing requests on a single class C block. You can also map blocks of internal addresses to an assigned external IP address. See /usr/src/contrib/ipfilter/rules/BASIC.NAT for some examples.

Combining address translation with packet filters can be confusing. Remember that incoming packets are translated before the packet is checked against the access list. Outbound packets are checked against the filter before being translated. You will want to write your rules appropriately; use the internal address as configured, not as translated. Put your configuration in /etc/ipnat.conf. You can then start ipnat with:

% ipnat -f /etc/ipnat.conf
As with ipf, if you want to flush the old rules and install updated ones, you simply do:

% ipnat -C && ipnat -f /etc/ipnat.conf
Service Redirection If you're using NAT, you'll want legitimate service requests that reach the outside of your firewall to be redirected to the proper port on an internal server. This can also support transparent proxies; outgoing requests on a particular port number can be redirected to a local proxy server.

To do this with ipnat, use the rdr keyword. Let's continue our example above by pointing mail requests to our outside IP to an inside IP: 192.168.66.5. Add the following to /etc/ipnat.conf:

rdr tun0 209.69.80.8/0 port 25 -> 192.168.66.5
Transparent Proxies ftp was written well before anyone thought firewalls or NAT would be necessity. Allowing naked ftp to pass through a non-NAT firewall requires fairly hideous rules in /etc/ipf.conf:

pass in quick proto tcp from any to any port \
   = ftp keep state 
  group 101
pass in quick proto tcp from any to any port \
   = ftp-data keep 
  state group 101
pass in quick proto tcp from any port = \
   ftp-data to any port > 
  1023 keep state group 301
The third rule allows the notorious “ftp backchannel” connection. Anyone can talk to any port above 1023 if their connection originates on port 20. This is bad; any cracker worthy of the name can make a connection originate from any port they like, and portscanning from port 20 is not uncommon. You can avoid the ftp backchannel by requiring your users to use passive ftp. You can also use IPFilter's built-in ftp proxy by adding this to /etc/ipnat.conf:

rdr ed0 0.0.0.0/0 port ftp -> 127.0.0.1 port ftp
This is an excellent example of a transparent proxy. The user doesn't need to change anything on his desktop; you control where service requests go, and you can change proxies as you desire. You can use similar rules to force traffic through other proxy servers. If you're running the TIS http-gw on port 80, for example, this rule will direct all requests for an outside http-port connection to the proxy:

rdr ed0 0.0.0.0/0 port 80 -> 127.0.0.1 port 80
Similar configurations can be used for any protocol to any proxy.

Packet Logging

The option log in an IPFilter rule requires ipmon, a separate program dedicated to logging ipf traffic. With the -s option, ipmon can send messages to syslogd, so you can set up a separate log host in standard UNIX fashion. IPFilter logs under the LOCAL0 facility.

Starting IPFilter at Boot Time

A simple shell script will start these at boot time for you. Simply write a brief shell script and put it in /usr/local/etc/rc.d.

% cat /usr/local/etc/rc.d/firewall.sh
#!/bin/sh

echo 'Starting firewall'
/sbin/ipf -f /etc/ipf.conf                #start protections
/sbin/ipnat -f /etc/ipnat.conf            #start address translation
/sbin/sysctl -w net.inet.ip.forwarding=1  #start packet forwarding
/sbin/sysctl -w kern.securelevel=3        #set securelevel
/usr/sbin/ipmon -s                        #start logging
echo 'firewall started'
%
Set your script mode 500, owned by root. The script name must end in .sh for the system loader to run it. You probably also want to protect it with chflags schg /usr/local/etc/rc.d/firewall.sh. It's also entirely possible to edit /etc/rc to run these commands. If you have to upgrade your firewall OS, however, you'll have to maintain these changes independently. See my article “Maintaining Patch Levels on Open-Source BSDs” (Sys Admin, September 1999).

Proxy Software

Proxy software makes network requests for a client, and returns the results to the client. Proxies can alter requests and perform access control. This article is too short to give full details on all proxy software available; this listing only gives a rough overview and pointers to more information. For an up-to-date list, check the search engine at http://www.freebsd.org/search/. Be sure to check all ports categories for proxies.

Some of the most well-known proxy packages include:

DeleGate (/usr/ports/security/delegate) -- This arbitrary TCP/UDP port redirection package is so full of buffer overruns that every BSD security team discourages its use in the strongest possible terms; it's mentioned here only as a warning.

FWTK (/usr/ports/security/fwtk) -- The TIS FireWall Tool Kit is a standard, well-tested proxy toolkit. It includes proxies for telnet, ftp, http, smtp, and X Window. These proxies are highly configurable and flexible. FWTK can be integrated with other proxies, such as Squid. The Web proxy (http-gw) can be configured to block certain types of content, such as JavaScript or ActiveX. Also, they do not require packet forwarding. If all your traffic runs through FWTK proxies, then you don't even require packet forwarding, further heightening security. See http://www.fwtk.org for more information. Note that the licensing on FWTK is rather restrictive; you cannot resell FWTK, or make a business out of installing FWTK.

JunkBuster (/usr/ports/www/ijb) -- JunkBuster can control which cookies are allowed to pass, and to where. This can be tedious to maintain in a large organization; but if cookie security is a concern, then this is your answer. JunkBuster can also strip banner ads from Web pages, making them load faster. See http://www.junkbuster.com for details.

PoPToP (/usr/ports/net/poptop) -- This is a Microsoft PPTP-aware VPN daemon, useful for making secure connections between your remote users and the internal network. See http://www.moretonbay.com/vpn/help.html for more information.

raproxy (/usr/ports/audio/raproxy) -- The reference RealAudio proxy provided by RealNetworks. This proxy is designed to be integrated with TIS' FireWall Tool Kit. For details, see http://docs.real.com/proxykit/.

rtsp-proxy-1.0 (/usr/ports/net/rtsp_proxy) -- This proxy will allow you to pass Apple's streaming Quicktime through your NAT firewall. See http://www.apple.com/ \
quicktime/resources/qt4/us/proxy/
for more information.

SKIP (/usr/ports/security/skip) -- Sun's Secure Connectionless Internet Protocol. Provides encryption of all IP services, without client changes. Useful between BSD and Sun boxes, or with Windows boxes with add-on client. See http://www.skip-vpn.org for more information.

Squid (/usr/ports/www/squid23) -- Squid is a caching Web proxy. It provides detailed logging and high performance. Squid is frequently updated; check the /usr/ports/www directory for recent versions. See http://www.squid-cache.org for details.

By combining these proxies as your security needs dictate, you can provide the exact level of security you need.

Post-Install Maintenance

Once you've installed and debugged your firewall, you'll want to inspect it periodically to be certain that it still meets your needs. FreeBSD has a variety of security tools to help you confirm this.

Any number of security-related sites carry auditing tools and security scanners. Some particularly useful ones already ported to FreeBSD include:

firewalk (/usr/ports/security/firewalk)

nmap (/usr/ports/security/nmap)

strobe (/usr/ports/security/strobe)

The most useful scanner I've seen is Nessus (/usr/ports/security/nessus). Nessus is fast, secure, highly configurable, and actively maintained. Nessus is especially useful in demonstrating the need for a firewall; once installed, it's easy to use. The reports generated are simple enough that anyone can understand the importance of any discovered holes. Check http://www.nessus.org for Nessus information. If you find security problems, adjust your firewall or your server software appropriately.

Conclusion

Building a firewall is not a task for the inexperienced systems administrator. It can be rewarding, however, and the results are just as effective, or more so, than commercial firewalls. The various BSDs have powerful firewalling features. Firewall techniques are also useful in protecting isolated hosts on the Internet. Since BSD hosts power some of the most high-traffic sites, this is a vital consideration. The firewall administrator has a wide choice of tools to enforce the organization's security policy.

About the Author

Michael Lucas is a networking and FreeBSD consultant working for the Great Lakes Technologies Group. He lives in Detroit, Michigan with his wife Liz, four gerbils, and assorted fish. He can be reached at: mwlucas@exceptionet.com.