Articles‎ > ‎

Advanced IPFW firewall configuration

posted Dec 22, 2008, 7:43 AM by Philip Rinehart   [ updated Dec 22, 2008, 7:45 AM by Greg Neagle ]
Since the release of Jaguar, Mac OS X 10.2, a firewall has been included with the operating system. This graphical firewall configuration is a good start, but may be insufficient for security requirements in an enterprise environment. Before proceeding to in-depth configuration, an examination of the technology behind the firewall is useful.So, what is the technology? It is a kernel based packet filter firewall, and uses the command ipfw. This command is part of the FreeBSD core of Mac OS X, and has been in the FreeBSD tree since 2.0. It has a multitude of options. One of the most important options is the ability for the firewall to be 'stateless' or 'stateful'. It is also important to note that ipfw is compiled into the Mac OS X kernel, and is therefore controlled by the kernel.

Note, that this method will not work for the built-in firewall in Leopard.  However, this method can be used in place of the Leopard firewall, as ipfw still exists on the system.

A stateless firewall will allow or deny incoming packets based on the port that the TCP or UDP request is arriving on. This behavior has a number of disadvantages, as any packet is allowed through on an open port in the firewall. The solution? Use a stateful firewall built into ipfw. Using this feature, a more granular approach to configuration of the firewall can be put in place. Rules are created dynamically, on demand, with a limited lifetime. With this configuration, a properly configured firewall will only allow legitimate traffic. One disadvantage is the complexity of configuring the firewall grows, requiring more time by the system administrator.

Before we dive into firewall configuration, how should the firewall be activated if the GUI is not being used? The answer is to use the sysctl command, altering the kernel state for ipfw. Here are the default values from the sysctl table:
net.inet.ip.fw.enable: 1
net.inet.ip.fw.one_pass: 1
net.inet.ip.fw.debug: 1
net.inet.ip.fw.verbose: 0
net.inet.ip.fw.verbose_limit: 0
net.inet.ip.fw.dyn_buckets: 256
net.inet.ip.fw.curr_dyn_buckets: 256
net.inet.ip.fw.dyn_count: 0
net.inet.ip.fw.dyn_max: 1000
net.inet.ip.fw.dyn_ack_lifetime: 300
net.inet.ip.fw.dyn_syn_lifetime: 20
net.inet.ip.fw.dyn_fin_lifetime: 20
net.inet.ip.fw.dyn_rst_lifetime: 5
net.inet.ip.fw.dyn_short_lifetime: 30
net.inet6.ip6.fw.enable: 1
net.inet6.ip6.fw.debug: 1
net.inet6.ip6.fw.verbose: 0
net.inet6.ip6.fw.verbose_limit: 0
Of the above variables, a few are of importance in this context. The first is net.inet.ip.fw.enable. A value of 1 enables the firewall. To enable firewall logging, which is important for ruleset troubleshooting, the value for net.inet.ip.fw.verbose should also be changed from 0 (disabled) to 1. This can be accomplished with the following command, sysctl -w net.inet.ip.fw.verbose=1. Once issued, all logged firewall traffic will be sent to the system log, /var/log/system.log. To make these settings permanent, create a new directory >/etc/sysctl.conf. Insert the lines net.inet.ip.fw.enable=1 and net.inet.ip.fw.verbose=1. On reboot, the sysctl variables will be set for an active firewall with logging.

The anatomy of a firewall rule

The syntax of ipfw is fairly straight forward. Note that all operations on the firewall with ipfw must be performed as the root user. Let's begin with a simple rule:
ipfw add 100 allow all from any to any via lo0
To understand how to configure the firewall more effectively, we will examine this command. The first bit of the command, ipfw add, uses ipfw to add a rule. After this, a numeric value is assigned. This value is arbitrary, and is only limited to the system administrator's imagination. However, rules are interpreted numerically. In this case, 100 is the initial rule. Additional rules should use higher numbers. The meat of the rule is next, allow all from any to any via lo0 This specifies allow all, allowing packets of any type. Next, from any to any, allows unrestricted traffic, and finally via lo0 which specifies the network interface. A complete listing of all interfaces is accomplished by typing ifconfig. lo0 is the loopback interface. Now knowing what we know, we can read the rule from left to right. Looking at the rule again:
ipfw add 100 allow all from any to any via lo0
It can be read as &quote;Add rule 100, allowing all traffic (UDP/TCP) from any address to any address through the loopback interface.

Common options

log

The log option can be used to cause any rule results to be sent to the system.log. This can be useful in determining how a rule is processed.

logamount

If a rule is creating excessive logging, the logamount option will restrict the number of messages sent to the system.log file. Adding logamount 500 would only allow 500 messages to be sent to the system logfile.

setup

This option applies to TCP packets only. It will match packets that have the SYN bit, set but no ACK bit.

established

This option applies to TCP packets only. It will match packets that have the RST or ACK bit set. Use of this rule can prevent man-in-the-middle TCP attacks.

frag

Match if the packet is a fragment and this is not the first fragment of the datagram, which can be used to circumvent ipfw.

icmptypes

With this option, icmptypes are matched in a rule. They are:
echo reply (0)
destination unreachable (3)
source quench (4)
redirect (5)
echo request (8)
router advertisement (9)
router solicitation (10)
time-to-live exceeded (11)
IP header bad (12)
timestamp request (13)
timestamp reply (14)
information request (15)
information reply (16)
address mask request (17)
address mask reply (18).

keep-state

When using a dynamic firewall, this option will cause the firewall to create a dynamic rule matching the exact parameters of the rule.

Moving to a dynamic firewall

To create a dynamic firewall, one of the first rules that should be added uses the check-state command. From the manual page, check-state &quote;Checks the packet against the dynamic ruleset. If a match is found then the search terminates, otherwise we move to the next rule. If no check-state rule is found, the dynamic ruleset is checked at the first keep-state rule.&quote; The reason this rule should be near the beginning of the ruleset is to activate dynamic rule set creation. The command is quite simple:
ipfw add 100 check-state
Any rule added after this rule will process the rules dynamically through any rule after rule 100.

A complete firewall ruleset

Below, I have included a complete firewall ruleset. This ruleset can be typed in, line-by-line, or added with the use of a shell script.
# Flush all the rules, quietly ipfw -qf flush

#Localhost rules

ipfw add 100 allow all from any to any via lo0
# Prevent any traffic to 127.0.0.1, common in localhost spoofing
ipfw add 110 deny log all from any to 127.0.0.0/8
ipfw add 120 deny log all from any to 127.0.0.0/8

#Testing rules, to find ports used by services if we aren't sure. These rules allow ALL traffic to pass through the firewall, disabling any subsequent rules
#ipfw add 140 allow log logamount 500 tcp from any to any
#ipfw add 150 allow log logamount 500 udp from any to any
#Start dynamic state filtering

ipfw add 200 check-state

#Filter out fragmented packets that do not have an offset of one which are automatically dropped

ipfw add 210 deny all from any to an frag in via en0

#Deny any ACK packets are not matched dynamically

ipfw add 220 deny tcp from any to any established in via en0

#Outbound web server ports, port 80 and 443 (SSL)

ipfw add 230 allow tcp from any to any 80 out via en0 setup keep-state
ipfw add 240 allow tcp from any to any 443 out via en0 setup keep-state

#nameserver ports
ipfw add 250 allow tcp from any to xx.xx.xx.xx 53 out via en0 setup keep-state
ipfw add 251 allow tcp from any to xx.xx.xx.xx 53 out via en0 setup keep-state
ipfw add 252 allow tcp from any to xx.xx.xx.xx 53 out via en0 setup keep-state
ipfw add 260 allow tcp from any to xx.xx.xx.xx 53 out via en0 setup keep-state
ipfw add 261 allow tcp from any to xx.xx.xx.xx 53 out via en0 setup keep-state
ipfw add 262 allow tcp from any to xx.xx.xx.xx 53 out via en0 setup keep-state
ipfw add 253 allow udp from any to xx.xx.xx.xx 53 out via en0 keep-state
ipfw add 254 allow udp from any to xx.xx.xx.xx 53 out via en0 keep-state
ipfw add 255 allow udp from any to xx.xx.xx.xx 53 out via en0 keep-state
ipfw add 263 allow udp from any to xx.xx.xx.xx 53 out via en0 keep-state
ipfw add 264 allow udp from any to xx.xx.xx.xx 53 out via en0 keep-state
ipfw add 265 allow udp from any to xx.xx.xx.xx 53 out via en0 keep-state

#allow traceroute out for diagnostics

ipfw add 270 allow udp from me to any 33434-33525 out via en0 keep-state
ipfw add 271 allow udp from any to any 33434-33525 in via en0 keep-state

#incoming traceroute, again for diagnosis if needed

ipfw add 280 allow log icmp from any to me icmptypes 3,11 in via en0 keep-state

#outgoing ping 

ipfw add 290 allow icmp from any to any out via en0 keep-state

#dhcp rules, use network address range syntax to allow DHCP requests from all of the 10.0.0.0 subnet.

ipfw add 300 allow udp from any 68 to 10.0.0.0/8 67 out via en0 keep-state
ipfw add 310 allow udp from 10.0.0.0/8 67 to any 68 in via en0 keep-state

#ssh rules
#Allow ssh out to any server
ipfw add 400 allow tcp from any to any 22 out via en0 setup keep-state
#Only allow ssh from specified server, 192.168.1.1 (for example), and only to this machine(me)
ipfw add 410 allow tcp from 192.168.1.1 to me 22 setup keep-state

#ntp rule, for setting of time automatically

ipfw add 420 allow udp from any to any 123 out via en0 keep-state
ipfw add 421 allow tcp from any to any 123 out via en0 setup keep-state

#ARD rules

#First we need to allow the Apple Remote Desktop protocol
ipfw add 430 allow udp from any to me 3283 in via en0 keep-state
ipfw add 431 allow udp from any to any 3283 out via en0 keep-state
# And also allow Apple VNC, otherwise we can't see what we are doing
ipfw add 440 allow tcp from any to me 5900 in via en0 keep-state setup

#Kerberos rules, to allow any tgt requests
ipfw add 450 allow tcp from any to me 88 in via en0 keep-state setup
ipfw add 451 allow udp from any to me 88 in via en0 keep-state
ipfw add 452 allow tcp from me to any 88 out via en0 keep-state setup
ipfw add 453 allow udp from me to any 88 out via en0 keep-state

#LDAP rules, for looking up directory information of any type, and the response

ipfw add 460 allow tcp from any to any 389 in via en0 keep-state setup
ipfw add 461 allow tcp from any to any 389 out via en0 keep-state setup
ipfw add 462 allow udp from any to any 389 in via en0 keep-state
ipfw add 463 allow udp from any to any 389 out via en0 keep-state

#inbound ping, again for diagnostic purposes

ipfw add 470 allow log icmp from any to me icmptype 0,8 in via en0 keep-state

#Allow Rendezvous packets (mDNS Responder)

ipfw add 480 allow udp from any 5353 to any in via en0 keep-state
#Multicast packet required by Rendezvous
ipfw add 481 allow ip from any to 224.0.0.251 out via en0 keep-state

#iChat rules, there are a lot!

ipfw add 500 allow udp from any to any 5060 out via en0 keep-state
ipfw add 501 allow udp from any to any 5190 out via en0 keep-state
ipfw add 502 allow tcp from any to any 5190 out via en0 setup keep-state
ipfw add 503 allow udp from any to any 5298 out via en0 keep-state
ipfw add 504 allow tcp from any to any 5298 out via en0 setup keep-state
ipfw add 505 allow udp from any to any 5353 out via en0 keep-state
ipfw add 506 allow udp from any to any 5678 out via en0 keep-state
ipfw add 507 allow udp from any to any 16384-16403 out via en0 keep-state
ipfw add 508 allow udp from any to me 5060 in via en0 keep-state
ipfw add 509 allow udp from any to any 5190 in via en0 keep-state
ipfw add 510 allow tcp from any to any 5190 in via en0 setup keep-state
ipfw add 511 allow udp from any to any 5297 out via en0 keep-state
ipfw add 512 allow udp from any to any 5297 in via en0 keep-state
ipfw add 513 allow udp from any to any 5298 in via en0 keep-state
ipfw add 514 allow tcp from any to any 5298 in via en0 setup keep-state
ipfw add 515 allow udp from any to any 5353 in via en0 keep-state
ipfw add 516 allow udp from any to any 5678 in via en0 keep-state
ipfw add 517 allow udp from any to any 16384-16403 in via en0 keep-state

#Samba rules

ipfw add 520 allow tcp from any to any 137,138,139 out via en0 keep-state setup
ipfw add 530 allow udp from any to any 137,138,139 out via en0 keep-state

#lpd rules, you do want printing, don't you?

ipfw add 540 allow tcp from any to any 515 out via en0 keep-state setup
ipfw add 550 allow udp from any to any 515 out via en0 keep-state

#Microsoft Active Directory Service rules - can't login without it!

ipfw add 560 allow tcp from any to any 3268 out via en0 keep-state setup
ipfw add 570 allow udp from any to any 3268 out via en0 keep-state

#radmind rules
ipfw add 580 allow tcp from any to any 6662 out via en0 keep-state setup

#KeyServer rules
ipfw add 590 allow udp from any to any 19283 out via en0 keep-state
ipfw add 591 allow tcp from any to any 19283 out via en0 keep-state setup

#RDP rules, for accessing Windows machines
ipfw add 604 allow udp from any to any 3389 out via en0 keep-state
ipfw add 603 allow tcp from any to any 3389 out via en0 keep-state setup
ipfw add 602 allow udp from any to any 3389 in via en0 keep-state
ipfw add 601 allow tcp from any to any 3389 in via en0 keep-state setup

#Outgoing smtp to send messages if needed
ipfw add 610 allow tcp from any to any 25 out via en0 keep-state setup

#Kpasswd rules (for binding to AD)
ipfw add 620 allow tcp from any to any 464 out via en0 keep-state setup
ipfw add 621 allow udp from any to any 464 out via en0 keep-state

# Luna Insight software rules for authentication
ipfw add 630 allow tcp from any to any 2840 out via en0 keep-state setup
ipfw add 640 allow tcp from any to any 2840 in via en0 keep-state setup

# Rules to allow Mathematica
ipfw add 650 allow tcp from any to any 16286 out via en0 keep-state setup

# Rules to allow IMP mail to function
ipfw add 660 allow tcp from any to any 8444 out via en0 keep-state setup

# Allow Sibelius license server
ipfw add 670 allow udp from any to any 7312 out via en0 keep-state

# Allow rtsp streaming media
ipfw add 680 allow tcp from any to any 554 out via en0 keep-state setup
ipfw add 681 allow udp from any to any 554 out via en0 keep-state
ipfw add 682 allow tcp from any to any 554 in via en0 keep-state setup
ipfw add 683 allow udp from any to any 554 in via en0 keep-state
#RSTP uses UDP ports to communicate, and need to be open. ipfw add 684 allow udp from 192.168.1.1 6970-32000 to me in via en0 keep-state

# Allow Filemaker Pro requests
ipfw add 690 allow tcp from any to any 591 out via en0 keep-state setup

#Monitor requests

ipfw add 700 allow tcp from any to any 8180 out via en0 keep-state setup

#Deny rules to filter out bogus packets

#Multicast packets that we want to ignore

ipfw add 700 deny tcp from any to 224.0.0.0/4 in via en0 setup keep-state
ipfw add 710 deny ip from any to 224.0.0.0/4 in via en0 keep-state

#External ICMP redirect requests

ipfw add 720 deny log icmp from any to any icmptype 5 in via en0 keep-state

#Prevent spoofing attacks, one should never see any traffic to and from me

ipfw add 730 deny log ip from me to me in via en0 keep-state

#Prevent ping echo attacks

ipfw add 740 deny log icmp from any to me icmptype 0,8 in via en0 keep-state

#Don't allow any TCP setup requests from the outside world

ipfw add 750 deny log tcp from any to any setup in via en0 keep-state

#Inbound NetBios traffic which just clogs up the logs

ipfw add 760 deny tcp from any to any 137,138,139 in via en0 setup keep-state
ipfw add 761 deny udp from any to any 137,138,139 in via en0 keep-state

#prevent ident requests

ipfw add 770 deny log tcp from any to me 113 in via en0 setup keep-state

#Attempt to prevent os fingerprinting, port 0 is commonly used for fingerprinting purposes

ipfw add 780 deny log tcp from any to any 0 in via en0 setup keep-state
ipfw add 781 deny log udp from any to any 0 in via en0 keep-state

#Default deny rule

ipfw add 10000 deny log logamount 500 all from any to any
This ruleset has been used in production, and successfully creates dynamic rules. It could be incorporated into a larger shell script.

Additional options

Two kernel MIB variables can be set using sysctl, called blackholes, to drop packets on arrival, thus speeding up the ipfw ruleset configuration. They are net.inet.tcp.blackhole and net.inet.udp.blackhole. The first variable can be set to 1 or 2. Setting the variable to 2 will cause any segment arriving on a closed port to be dropped without returning a RST. Setting it to 1 allows a RST packet to be sent. In the case of the second variable net.inet.udp.blackhole this variable turns off the sending of an ICMP port unreachable message in response to a UDP datagram which arrives on a port where there is no socket listening. The UDP blackhole will prevent incoming traceroute requests.

Conclusion

Many other options exist for ipfw, and can be found in the manual page, including:
  • The divert option will allow all traffic to be re-routed to a different port
  • Probability matching
  • Pipes can limit and control bandwidth using the proper ruleset
  • UID/GID based filtering. Packets can be controlled based on the UID/GID of the process
ipfw is an incredibly powerful tool with a rich history of development. Using the techniques presented here, a system administrator should be able to configure a firewall specific to the needs of the enterprise environment.and not only filters outbound traffic, but also filters inbound traffic. The next step is to start creating your own firewall!
Comments