I attended Jay Beale’s Discovering OS X weaknesses and fixing them with the new Bastille Linux port at Defcon last week. Jay did a great job presenting, and pointed out several HUGE flaws that are present with the default OS X “stealth” firewall rule set. The first major problem Jay pointed out was the fact that all UDP datagrams with source port 67 or 5353 are allowed in (this allows you to talk to ntpd and cups, which have a rocky security history). The second major flaw is the fact that the default configuration blocks ICMP type code 8 (ICMP echo requests), but allows all other ICMP traffic in. And finally, OS X defaults to an allow any rule, which allows cruft like bonjour and the service locator to pollute your network with the version of OS X you are running, and the hardware architecture you are running on (this is a shell coders dream!). I take security rather seriously, so I sat down the night I got home and read the ipfw manual page, and created the following firewall rule set to deny all traffic by default, and allow a few trusted services out:
$ cat /etc/rc.firewall
#!/bin/sh
# Variables to simplify maintenance (these are comma delimited)
DNS_SERVERS="10.1.1.1"
# Enable firewall logging
/usr/sbin/sysctl -w net.inet.ip.fw.verbose=1
# Flush existing rules
/sbin/ipfw -f flush
# If the rule was added to the dynamic rule table, let it in
/sbin/ipfw add check-state
# Allow traffic to flow on the loopback interface
/sbin/ipfw add allow all from any to any via lo0
# Allow established connections
/sbin/ipfw add allow tcp from any to any established
# Allow SSH connections
/sbin/ipfw add allow tcp from me to any 22 keep-state
# Allow non-secure web traffic
/sbin/ipfw add allow tcp from me to any 80 keep-state
# Allow secure web traffic
/sbin/ipfw add allow tcp from me to any 443 keep-state
# Allow secure LDAP traffic
/sbin/ipfw add allow tcp from me to any 636 keep-state
# Allow IMAPS
/sbin/ipfw add allow tcp from me to any 993 keep-state
# Allow me to get to my DNS servers
/sbin/ipfw add allow udp from me to ${DNS_SERVERS} 53 keep-state
# Optionally allow ICMP traffic out
# /sbin/ipfw add allow icmp from me to any out keep-state
# Deny everything else
ipfw add deny log ip from any to any
To enable the policy at startup, you need to place the rules listed above in a file, and make the file executable. This blog entry assumes the rules were placed in the file /etc/rc.firewall. Next, you will need to create an entry in the system startup folder. Each startup item contains a script to start and stop the service, and a property file to control when and how the service starts. To enable the firewall policy listed above, we can create a file called /Library/StartupItems/Firewall/Firewall with start, stop and restart actions:
$ cat /Library/StartupItems/Firewall/Firewall
#!/bin/sh
##
# Firewall
##
. /etc/rc.common
case "$1" in
start)
ConsoleMessage "Starting Firewall"
# Activate the firewall rules
/etc/rc.firewall > /dev/null
;;
stop)
echo "Stopping Firewall..."
/sbin/ipfw -f flush
;;
restart)
ConsoleMessage "Retarting Firewall"
# Activate the firewall rules
/etc/rc.firewall > /dev/null
;;
esac
exit 0
In addition to the script listed above, you will also need to create a properties file to tell OS X when the service should start, and any dependencies that need to be online before the service is started. The properties file should be placed in the same directory as the startup script, and named StartupParameters.plist. The following property file can be used along with the Firewall startup script listed above:
$ cat /Library/StartupItems/Firewall/StartupParameters.plist
{
Description = "Firewall";
Provides = ("Firewall");
Requires = ("NetworkExtensions","Resolver");
OrderPreference = "Late";
Messages =
{
start = "Starting firewall";
stop = "Stopping firewall";
};
}
Once all three files are in place, you can reboot the machine, and run ‘ipfw show’ as the root user to make sure the policy is installed. Daniel Cote has a great write up on building robust OS X firewall (ipfw) rulesets (I didn’t need some of the bells and whistles provided by Daniel’s firewall.sh.current script, so I reduced the rules to exactly what I need to filter inbound and outbound traffic). The Firewall and StartupParameters.plist files were taken from the firewall tarball on Daniel’s website, and I would like to thank him for putting together such an awesome website!