The Innamincka Affair: Love. Lies. Mortal Danger. A lot can happen with an affair at Innamincka. Rebecca Boucher is a respected junior partner in a London law firm. When she’s sent to Australia to meet with the owner of a vast cattle property, she’s expecting a straightforward legal matter. But what she finds is Cooper Read More ...
Protect Your Mac with PF, the All Powerful Firewall
PF
This post is material that comes from a slightly earlier version but is very relevant none the less. However, you should be aware that an OS Update will wipe out your existing pf.conf … very bad, because you won’t notice. What you should do is copy your pf.conf into a file that won’t be destroyed, for example, your domain-name.pf.conf and then, if your plist file, use that file name, not pf.conf.
This Mac is a Mac Mini 2012. July. It is NOT running Apple’s Server App. It IS running all the components of a server that I have either built myself or added with Homebrew. These include the following.
Apache 2.4, Postfix, Dovecot, Spamassassin, Amavis and numerous other small binaries needed to support them, and other things I’m doing.
This set of configurations are valid for OSX though, up to at least the latest macOS High Sierra Version 10.13.6. When the new release appears, I’ll check and update this post. That is, the sample pf.conf that follows this “tutorial” which in some cases is inaccurate… but mine works. It contains IPv6 settings as well as IPv4, and some settings that I’m working on as an experiment.
Remember, pf won’t work even if you load it, unless it’s Enabled. See below, and in the sample pf.conf.
Don’t forget to read the onboard man pages. $man pf.conf and $man pfctl
The Main Description
Mac OS X 10.6 (and earlier) came with IPFW, a port of FreeBSD’s stateful firewall[1]. IPFW was deprecated in OS X 10.7, and was completely removed in OS X 10.10; it was replaced with PF. PF (Packet Filter) is OpenBSD’s system for filtering TCP/IP traffic and doing Network Address Translation[2]. PF in OS X, however, appears to be based on the FreeBSD port of PF[3], but with some notable additions (see below). Like FreeBSD 9.X and later, OS X appears to use the same version of PF as OpenBSD 4.5. Note that the latest OpenBSD version is 5.6 (as of January 2015);and the configuration syntax for PF changed around 4.6/4.7.
Apple has enhanced PF so that various system components might choose to enable and disable PF, as indicated by the following snippet in /etc/pf.conf:
# This file contains the main ruleset, which gets automatically loaded # at startup. PF will not be automatically enabled, however. Instead, # each component which utilizes PF is responsible for enabling and disabling # PF via -E and -X as documented in pfctl(8). That will ensure that PF # is disabled only when the last enable reference is released.
These two flags, -E and -X, are absent from pfctl on other BSDs. Here’s how they are documented in pfctl(8):
-E Enable the packet filter and increment the pf enable reference count. -X token Release the pf enable reference represented by the token passed. -s References Show pf-enable reference statistics (pid/name of enabler, token, timestamp).
The main PF configuration file is /etc/pf.conf, which defines the following main ruleset by default in OS X 10.9 & 10.10:
scrub-anchor "com.apple/*" nat-anchor "com.apple/*" rdr-anchor "com.apple/*" dummynet-anchor "com.apple/*" anchor "com.apple/*" load anchor "com.apple" from "/etc/pf.anchors/com.apple"
The main ruleset loads sub rulesets defined in /etc/pf.anchors/com.apple, using anchor[4]:
anchor "200.AirDrop/*" anchor "250.ApplicationFirewall/*"
The launchd configuration file for PF is /System/Library/LaunchDaemons/com.apple.pfctl.plist. PF is disabled by default:
$ sudo pfctl -s info No ALTQ support in kernel ALTQ related functions disabled Status: Disabled Debug: Urgent
Application Firewall
OS X v10.5.1 and later include Application Firewall that allow the users to control connections on a per-application basis (rather than a per-port basis)[5]. Application Firewall is disabled by default.
After enabling the Application Firewall (System Preferences -> Security & Privacy ->
-> ), you’ll find PF is enabled too:$ sudo pfctl -s info Status: Enabled for 0 days 00:02:03 Debug: Urgent $ sudo pfctl -s References TOKENS: PID Process Name TOKEN TIMESTAMP 618 socketfilterfw 9813589183660731843 0 days 00:03:31 $ sudo pfctl -a com.apple -s rules anchor "200.AirDrop/*" all anchor "250.ApplicationFirewall/*" all
Apparently Application Firewall enables PF using pfctl -E. In addition to its own rules, Application Firewall generates a set of dynamic rules (sub ruleset) for PF through anchor point com.apple/250.ApplicationFirewall. At this stage, the sub ruleset is empty, which got someone confused.
But if either “Enable stealth mode” or “Block all incoming connections” is checked in
, dynamic rules for PF will indeed be created:$ sudo pfctl -a com.apple/250.ApplicationFirewall -s rules scrub in all fragment reassemble block drop in inet proto icmp all icmp-type echoreq block drop in inet proto icmp all icmp-type echoreq block drop in inet6 proto ipv6-icmp all icmp6-type echoreq
Note there is a bug in Apple’s implementation of PF! According to pfctl(8):
If the anchor name is terminated with a `*’ character, the -s flag will recursively print all anchors in a brace delimited block.
but it produces an error instead:
$ sudo pfctl -a 'com.apple/*' -sr anchor "*" all { pfctl: DIOCGETRULES: Invalid argument } anchor "*" all { pfctl: DIOCGETRULES: Invalid argument }
We have to use the full anchor path:
$ sudo pfctl -v -s Anchors com.apple com.apple/200.AirDrop com.apple/200.AirDrop/Bonjour com.apple/250.ApplicationFirewall $ sudo pfctl -a "com.apple/200.AirDrop/Bonjour" -sr pass in on p2p0 inet6 proto udp from any to any port = 5353 keep state pass out on p2p0 proto tcp all flags any keep state
As you can see, a set of dynamic PF rules is created for AirDrop too. I surmise they are still created by Application Firewall, because according to the output of pfctl -s References, PF has only been enabled once, by Application Firewall.
Command Line
Besides using the Security & Privacy Preference pane, you can also configure the Application Firewall from the command line. The utilities for Application Firewall are stored in /usr/libexec/ApplicationFirewall. The default configuration file is /usr/libexec/ApplicationFirewall/com.apple.alf.plist; and the running configuration file is /Library/Preferences/com.apple.alf.plist[6].
▸ Stopping and starting Application Firewall is easy enough, using launchd[7]. To stop:
$ sudo launchctl unload /System/Library/LaunchAgents/com.apple.alf.useragent.plist $ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.alf.agent.plist
▸ To start:
$ sudo launchctl load /System/Library/LaunchDaemons/com.apple.alf.agent.plist $ sudo launchctl load /System/Library/LaunchAgents/com.apple.alf.useragent.plist
We can configure the settings of Application Firewall using socketfilterfw:
usage: /usr/libexec/ApplicationFirewall/socketfilterfw [-c] [-w] [-d] [-l] [-T] [-U] [-B] [-L] \ [-a listen or accept] [-p pid to write] [--getglobalstate] [--setglobalstate on | off] \ [--getblockall] [--setblockall on | off] [--listapps] \ [--getappblocked <path>] [--blockapp <path>] [--unblockapp <path>] \ [--add <path>] [--remove <path>] [--getallowsigned] [--setallowsigned] \ [--getstealthmode] [--setstealthmode on | off] \ [--getloggingmode] [--setloggingmode on | off] \ [--getloggingopt] [--setloggingopt throttled | brief | detail]
pflog
Logging support for PF is provided by pflog. The pflog interface is a pseudo-device which makes visible all packets logged by PF. Logged packets can easily be monitored in real time by invoking tcpdump on the pflog interface.
▸ Create a pflog interface:
$ man 4 pflog $ sudo ifconfig pflog0 create
▸ Monitor all packets logged by PF:
$ sudo tcpdump -n -e -ttt -i pflog0
▸ Destroy the pflog interface when done:
$ sudo ifconfig pflog0 destroy
precedence
If two firewalls, Application Firewall & PF, are running, you may wonder whose rules take precedence. Let’s find out.
The logs of Application Firewall are saved in /var/log/appfirewall.log. You’ll see a lot entries like the following, repeating roughly 2 times per minute on my iMac:
Jan 20 00:03:35 manjusri.local socketfilterfw[228] <Info>: Dropbox109: Deny UDP CONNECT (in:22 out:0) Jan 20 00:03:35 manjusri.local socketfilterfw[228] <Info>: ntpd: Deny UDP CONNECT (in:2 out:0) Jan 20 00:03:35 manjusri.local socketfilterfw[228] <Info>: netbiosd: Deny UDP CONNECT (in:13 out:0) Jan 20 00:03:44 manjusri.local socketfilterfw[228] <Info>: Stealth Mode connection attempt to UDP 3 time Jan 20 00:03:44 manjusri.local socketfilterfw[228] <Info>: Stealth Mode connection attempt to TCP 2 time
Add the following as the first rule of /etc/pf.conf:
set skip on lo0
Add the following 3 lines to /etc/pf.conf (to block incoming traffic but allow outgoing traffic):
pass in quick proto udp to any port 5353 block in pass out quick
The first rule is to allow incoming Bonjour traffic. In a hostile environment, e.g., a public WiFi, we’ll put the above 3 lines at the end of the file to block all incoming traffic, in which case, the sub rulesets in anchor “com.apple” will have no effect! Note For each packet or connection evaluated by PF, the last matching rule in the ruleset is the one which is applied. In work environment, you can put the 3 lines right above the line:
anchor "com.apple/*"
Reload /etc/pf.conf:
$ sudo pfctl -f /etc/pf.conf
Show the currently loaded filter rules:
$ sudo pfctl -s rules scrub-anchor "com.apple/*" all fragment reassemble block drop in all pass out all flags S/SA keep state anchor "com.apple/*" all
Check /var/log/appfirewall.log again. You’ll find no new log entry for Application Firewall appears in the file.
So one can conclude that PF rules are applied first, then the rules for Application Firewall.
SSH
To enable OpenSSH server on OS X, in the Sharing Preference pane of System Preferences, check “Remote Login”. Or from the command line:
$ sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
launchctl(1) says such about the -w flag:
-w Overrides the Disabled key and sets it to false. In previous versions, this option would modify the configuration file. Now the state of the Disabled key is stored elsewhere on-disk.
but where exactly is the ‘elsewhere’? After some digging, I find it is /private/var/db/launchd.db/com.apple.launchd/overrides.plist.
However, I don’t like the default configuration for sshd. I prefer to have password authentication disabled. Add the following options to /etc/sshd_config:
PermitRootLogin no PasswordAuthentication no ChallengeResponseAuthentication no
Restart sshd:
$ sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist $ sudo launchctl load /System/Library/LaunchDaemons/ssh.plist
Note to allow incoming traffics to the OpenSSH server through Application Firewall, you must allow incoming connections for /usr/libexec/sshd-keygen-wrapper, either in System Preferences -> Security & Privacy -> -> , or from the command line:
$ sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /usr/libexec/sshd-keygen-wrapper
Configuring PF
The Application Firewall’s rule of allowing all incoming incoming traffics to the OpenSSH server offers no defense against brute force attack. Leaving the ssh port open on the internet, the server will get thousands of brute force login attempts each day. PF provides an elegant solution to this problem.
▸ Append the following lines to /etc/pf.conf (see Section 30.3.3.5. Using Overload Tables to Protect SSH of FreeBSD Handbook for an explanation):
table <bruteforce> persist block quick from <bruteforce> pass in inet proto tcp to any port ssh \ flags S/SA keep state \ (max-src-conn 5, max-src-conn-rate 5/5, \ overload <bruteforce> flush global)
▸ Reload /etc/pf.conf:
$ sudo pfctl -f /etc/pf.conf
Over time, the table bruteforce will be filled by overload rules and its size will grow incrementally, taking up more memory. We can expire table entries using pfctl. For example, this command will remove bruteforce table entries which have not been referenced for a day (86400 seconds):
$ sudo pfctl -t bruteforce -T expire 86400
To automate the process, let’s create a timed job using launchd that runs the above command once per day[8].
▸ Create a launchd configuration file /Library/LaunchDaemons/edu.ucsc.manjusri.pfctl-expire.plist, with the following content:
<?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>edu.ucsc.manjusri.pfctl-expire</string> <key>WorkingDirectory</key> <string>/var/run</string> <key>UserName</key> <string>root</string> <key>GroupName</key> <string>wheel</string> <key>Program</key> <string>/sbin/pfctl</string> <key>ProgramArguments</key> <array> <string>pfctl</string> <string>-t</string> <string>bruteforce</string> <string>-T</string> <string>expire</string> <string>86400</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>10</integer> <key>Minute</key> <integer>10</integer> </dict> </dict> </plist>
▸ Start the timed job:
$ sudo launchctl load /Library/LaunchDaemons/edu.ucsc.manjusri.pfctl-expire.plist
P.S. There are a few articles on the Internet on using PF on Mac OS X, but they often bypass the configuration file /etc/pf.conf[9]. If one takes that route, one must disable the Application Firewall. Otherwise Application Firewall will enable PF using the ruleset in /etc/pf.conf. Only one ruleset will get loaded at last and become effective; but which one wins will probably be indeterministic or at least could be a surprise. I choose the approach described in this article, because:
- I alway like to try something different
- I prefer layered defense. In this case, I have 2 firewalls running on the Mac.
References
Sample pf.conf called by my .plist
# sample firewall rules # to enable: # pfctl -e -f chalmers.pf.conf # to disable: # pfctl -d # To check syntax. sudo pfctl -vnf /etc/chalmers.pf.conf set block-policy drop set fingerprints "/etc/pf.os" set ruleset-optimization basic set skip on lo0 # Normalization # Scrub incoming packets scrub in all no-df # # com.apple anchor point # pflog_logfile="/var/log/pflog" scrub-anchor "com.apple/*" nat-anchor "com.apple/*" rdr-anchor "com.apple/*" dummynet-anchor "com.apple/*" anchor "com.apple/*" load anchor "com.apple" from "/etc/pf.anchors/com.apple" anchor "emerging-threats" load anchor "emerging-threats" from "/etc/pf.anchors/emerging-threats" ### START Custom Rules ### AllowedIn = "{ 192.168.0.0/24, 192.168.1.0/24 }" tablepersist file "/etc/badguys1" file "/etc/badguys2" # to add a host to the bad list # pfctl -t badhosts -Tadd # to see the current bad hosts # pfctl -t badhosts -T show # table persist # block the jerks block on en1 from to any block on en0 from to any table persist # Block by default #block in log # Block to/from illegal destinations or sources block in log quick from no-route to any # Allow critical system traffic pass in quick inet proto udp from any port 67 to any port 68 # Allow ICMP from Mac Mini LAN #pass in log proto icmp from 66.209.67.0/24 # Allow all lan traffic in (if you are on a LAN that is yours) #pass in from 10.0.0.0/24 to any # Allow all VPN traffic in (if there is a VPN LAN) #pass in from 10.0.1.0/24 to any # allow good places everything, if you want pass in from { } to any pass in inet proto tcp from to any pass in inet proto udp from to any # Allow outgoing traffic pass out proto tcp from any to any keep state pass out proto udp from any to any keep state # allow stuff you want pass in proto tcp from any to any port 80 pass in proto tcp from any to any port 443 pass in proto tcp from any to any port 21 pass in proto tcp from any to any port 2121 pass in proto tcp from any to any port 22 pass in proto tcp from any to any port 110 pass in proto tcp from any to any port 143 pass in proto tcp from any to any port 465 pass in proto tcp from any to any port 587 pass in proto tcp from any to any port 993 pass in proto tcp from any to any port 995 # block the jerks block on en1 from { } to any block on en0 from { } to any pass proto ipv6-icmp from any to any # Additional IPv6 settings. ext_if="en0" icmp6_types="{ 2, 128 }" # packet too big, echo request (ping6) # Neighbor Discovery Protocol (NDP) (types 133-137): # Router Solicitation (RS), Router Advertisement (RA) # Neighbor Solicitation (NS), Neighbor Advertisement (NA) # Route Redirection icmp6_types_ext_if="{ 128, 133, 134, 135, 136, 137 }" tcp46_services="{ 22 }" # ssh; we allow this in for ALL machines for TCP[46] tcp46_services_ext_if="{ 53 }" # DNS zone transfer udp6_services_ext_if="{ 53, 123, 1194, 546}" # 546 == dhcpv6-client # IPv6 ... These don't work. #pass in quick on $ext_if inet6 proto ipv6-icmp icmp6-type $icmp6_types keep state #pass in quick on $ext_if inet6 proto ipv6-icmp from any to #{ ($ext_if ), ff02::1/16 } icmp6-type $icmp6_types_ext_if keep state #pass in quick on $ext_if inet6 proto tcp from any to any port #$tcp46_services flags S/SA keep state #pass in quick on $ext_if inet6 proto tcp from any to #( $ext_if ) port $tcp46_services_ext_if flags S/SA #keep state #pass in quick on $ext_if inet6 proto udp from any to #( $ext_if ) port $udp6_services_ext_if keep state table persist block quick from pass in inet proto tcp to any port ssh \ flags S/SA keep state \ (max-src-conn 5, max-src-conn-rate 5/5, \ overload flush global) ### END Custom Rules ###
Last Updated on October 9, 2018 by @R_A_Chalmers
I am a sysadmin researching PF because I need the granularity (per interface, per subnet, etc.) that is lacking in the MacOS applicaition firewall. Many thanks for this post – it is the most informative that I have found on the subject and delightfully out of place on this site!
try this one.
https://robert-chalmers.uk/2018/10/03/protect-your-mac-with-pf-the-all-powerful-firewall/
What a delightful mind that created this tutorial. Thank you! Robert.
As a fun experiment, I was trying to block a range of addresses for Google and YouTube to see just how hobbled the internet would be.
Also, my strategy would be to pick as my Big Brother, Apple, who spies via my devices, then block Google except for the occasional proxy, use an alt DNS against my ISP and ditch my VPN for general surfing.
But I digress.
I cannot seem to get around:
> No ALTQ support in kernel
> ALTQ related functions disabled
even when enabled via System Preferences Firewall.
(To me that looks like a kernel recompile would be needed?)
I’m trying to add two block rules to /etc/pf.conf
block return from 74.125.0.0/16 to any
block return from 173.194.0.1/16 to any
I did finally get a SIP error which says to me, “perhaps it’s time to move along to other forms of entertainment”….
So,
> sudo launchctl unload /System/Library/LaunchDaemons/com.apple.alf.agent.plist
results in
> Unload failed: 150: Operation not permitted while System Integrity Protection is engaged
Any quick advice? Is my syntax correct?
If the only work around is to disable SIP, I might be interested to know how, but probably would not do that at this time.
(Could you also please ping by e-mail me if you respond (if not done automatically by comments). )
Thank you.
Sorry for the delay. Disabling SIP is usually easy, but I”m not too sure with Ventura. So if you can disable SIP, try it out, then re-enable it if you feel unsure.