Configuring the OSX firewall to block specific ports in 10.8+ using pf
Apple changed the firewall from ipfw to pf sometime around 10.8. Sometimes, one must block specific ports or perform more advanced firewalling than ApplicationFirewall allows. For example, I sought to block portmap reflection attacks on UDP 111, which cannot be done using ApplicationFirewall or the Security system preference.
I found this discussion of pf on OSX very helpful in building these rules: http://blog.scottlowe.org/2013/05/15/using-pf-on-os-x-mountain-lion/
1) Create your own pf.conf file. We create a different pf.conf file rather than modifying the system one because Apple has a habit of overwriting system files in updates.
sudo cp /etc/pf.conf /etc/my.pf.conf
sudo nano /etc/my.pf.conf
2) Add these lines to the bottom:
anchor "myrules"
load anchor "myrules" from "/etc/pf.anchors/my.rules"
3) Then save and exit. Note that OSX has a tendency to replace " with “ which will cause issues. Quotes must be regular quotes and not the fancy OSX ones.
4) Now, create your ruleset. This is a basic set that allows SSH, HTTPD and AFP only. Create this file in /etc/pf.anchors/my.rules
sudo nano /etc/pf.anchors/my.rules
set block-policy drop
set fingerprints "/etc/pf.os"
set ruleset-optimization basic
set skip on lo0
# Scrub incoming packets
scrub in all no-df
# Antispoof
antispoof log quick for { lo0 en0 en2 }
# Block to/from illegal destinations or sources
block in log quick from no-route to any
# Block by default
block in log
# Block to/from illegal destinations or sources
block in log quick from no-route to any
# Block portmap reflection attacks
block in quick proto udp from any to port 111
# Allow critical system traffic
pass in quick inet proto udp from any port 67 to any port 68
# allow ssh, http, AFP
pass in quick inet proto tcp from any to port 22
pass in quick inet proto tcp from any to port 80
pass in quick inet proto tcp from any to port 548
# Allow outgoing traffic
pass out inet proto tcp from any to any keep state
pass out inet proto udp from any to any keep state
5) Test the ruleset. You should see your rules printed. If there are any syntax errors or etc here, fix them.
sudo pfctl -vnf /etc/my.pf.conf
6) Create a new LaunchDaemon plist file for the firewall to load on boot
sudo nano /Library/LaunchDaemons/my.pf.plist
7) Fill the file with this XML, and note you may have to modify the location of your custom pf config file generated in step 1 if you used something other than /etc/my.pf.conf
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>my.pf</string>
<key>Program</key>
<string>/sbin/pfctl</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/pfctl</string>
<string>-e</string>
<string>-f</string>
<string>/etc/my.pf.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceDescription</key>
<string>FreeBSD Packet Filter (pf) daemon</string>
<key>StandardErrorPath</key>
<string>/var/log/pf.log</string>
<key>StandardOutPath</key>
<string>/var/log/pf.log</string>
</dict>
</plist>
8) Change ownership of the file to root
sudo chown root /Library/LaunchDaemons/my.pf.plist
9) Fire it up with launchctl!
sudo launchctl load /Library/LaunchDaemons/my.pf.plist
10) And verify it’s loaded: