Configuring IPFW Firewall on OS X
14 March 2012
The latest versions of Mac OS X, being based on BSD style Unix, have a lot of powerful features that are legacy to many Unix operating systems. One is the ipfw firewall. OS X actually has two firewalls by default, an application firewall that blocks access to specific programs, and the ipfw firewall, which is a much lower level firewall that operates by inspecting inbound packets and allowing or denying them based on source IP, destination IP, port and protocol. This allows a much finer grain of control for remote access to an OS X computer.
This tutorial is designed to walk you through the steps necessary to configure ipfw with basic filtering to allow only connections to certain services. You can customize the scripts listed below to suit your own needs, including limiting connections to specific trusted hosts.
Configuring the Ipfw Firewall
In order to get the ipfw firewall working three separate scripts are required. The first is a custom firewall ruleset. This will specify the rules that you wish to enforce on network access to and from your machine. ipfw can do inbound and outbound filtering, which is really handy, but it's very sensitive to the order of rules. The first rule matched by a packet is the one that applies, so if a packet matches two rules in ipfw, only one will govern the action of the firewall: the first rule. The second file that you'll need to add to your system is a shell script that flushes existing firewall rules and loads up your new ruleset. This is a relatively straightforward control file that is just a BASH shell script. The third file is an XML file that serves as instructions to the Launchd daemon on the OS X system. In many ways this file is analogous to an init script on any other Unix or Linux type system. When the operating system boots, it searches in the Launchd directory for XML files that specify which services should be started. The new XML file that you'll add will tell your OS X system to run the shell script, which will flush existing firewall rules and load up your custom rules. Once configured, the ipfw firewall can only be modified by changing one of the three new files. The ipfw has a higher priority and trumps the application firewall, so some user education may be necessary to inform users that even if they allow access via the application firewall via the GUI settings manager, the ipfw firewall will block access to the application until it is modified.Setting up the Firewall
The first step in installing your new ipfw firewall is to develop a firewall ruleset. The following is a sample ruleset that allows no inbound traffic except for responses to connections initiated by the host, and SSH connections. To create this file type:$ sudo vi /etc/firewall.confYou'll need to be able to sudo to be able to do this. Once vi is started copy the following text into the file:
# Sample IPFW config # by Justin C. Klein Keane <jukeane@madirish.net> # NB: This script is initiated by /usr/local/sbin/firewall.sh # firewall.sh is in turn controlled by launchd from the # config at /Library/LaunchDaemons/com.apple.firewall.plist # # Permit loopback add 100 allow ip from any to any via lo* # Permit outbound connections to maintain state add 120 allow tcp from any to any out keep-state add 130 allow udp from any to any out keep-state # Permit incoming SSH add 140 allow tcp from any to any dst-port 22 # Default deny with logging (final rule) add 65534 deny log logamount 1000 ip from any to any inSave and quit the file and then change the permissions on the file like so:
$ sudo chown root:admin /etc/firewall.confNow you need to create the shell script that will load up these custom rules. To do this edit the following file, noting that you may have to create the /usr/local and /usr/local/sbin directories if they don't already exist:
$ sudo vi /usr/local/sbin/firewall.shCopy and paste the following text into this shell script:
#!/bin/sh # Flush existing rules /sbin/ipfw -q flush # Run IPFW and load custom rules /sbin/ipfw -q /etc/firewall.conf # Enable detailed logging to syslog /usr/sbin/sysctl -w net.inet.ip.fw.verbose=1Now that we have the shell script we need to change the permissions to protect it and make it executable like so:
$ sudo chown root:admin /usr/local/sbin/firewall.sh $ sudo chmod 544 /usr/local/sbin/firewall.shFinally you need to create the Launchd script. To do this create a new file like so:
$ sudo vi /Library/LaunchDaemons/com.apple.firewall.plistOnce this is done copy and paste the following XML into the file:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0 //EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.apple.firewall</string> <key>ProgramArguments</key> <array> <string>/usr/local/sbin/firewall.sh</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>Save and quit this file. Next change the permissions on the file appropriately using:
$ sudo chown root:admin /Library/LaunchDaemons/com.apple.firewall.plist
Testing the Configuration
And you should be all set. You can test out your new configuration by running an NMAP scan of your machine and noting the output:$ nmap target.domain.tld Starting Nmap 5.51 ( http://nmap.org ) at 2012-03-14 08:33 EDT Nmap scan report for target.domain.tld (127.0.0.1) Host is up (0.0023s latency). Not shown: 997 closed ports PORT STATE SERVICE 22/tcp open ssh 88/tcp open kerberos-sec 5900/tcp open vnc Nmap done: 1 IP address (1 host up) scanned in 7.37 secondsNow we can reload our firewall with the new custom rules using the following command:
$ sudo launchctl load /Library/LaunchDaemons/com.apple.firewall.plistAfter the firewall is reloaded you can check the ipfw rules using the following:
$ sudo ipfw show 00100 0 0 allow ip from any to any via lo* 00120 59 4460 allow tcp from any to any out keep-state 00130 2 256 allow udp from any to any out keep-state 00140 1 100 allow tcp from any to any dst-port 22 65534 0 0 deny log logamount 1000 ip from any to any in 65535 0 0 allow ip from any to anyFinally you can re-scan the machine with NMAP to confirm that the changes have taken place:
$ nmap -Pn target.domain.tld Starting Nmap 5.51 ( http://nmap.org ) at 2012-03-14 08:38 EDT Nmap scan report for target.domain.tld (127.0.0.1) Host is up (0.0094s latency). Not shown: 999 filtered ports PORT STATE SERVICE 22/tcp open ssh Nmap done: 1 IP address (1 host up) scanned in 4.27 secondsAt this point your machine is pretty well locked down. The ipfw configuration will survive reboots as well as tweaks in the GUI settings for the application firewall. If you want to be even more careful we might limit the SSH connections to specific machines. For instance, if we only wanted to allow connections from the local subnet we could change the rule in firewall.conf for SSH to the following:
add 140 allow tcp from 10.0.0.0/24 to any dst-port 22Note the use of CIDR notation. This will limit SSH connections to the range 10.0.0.1-254 and deny inbound SSH connections from any other host. This can be extremely useful in permitting only connections from trusted machines.