Egress filtering is an immensely powerful security control but it's not so
straightforward to do it well. If any malware manages to execute on your system
one of the first things it's likely going to try to do is call home and
establish a C2 channel. With effective egress filtering you can break this link
in the attack chain and stop it dead in its tracks.
There are two well-known products which do egress filtering on macs - Little
Snitch and LuLu. LuLu is made available by the awesome Patrick Wardle on his
website - https://objective-see.com - along with a load of other very clever
This post is mostly going to discuss LuLu but the approach taken here may well
work just as well with Little Snitch. LuLu is free and very cleverly made, it
even allows you to configure regexes for web urls that applications are allowed
to connect to. It does have one major limitation imposed by the operating
system though which is mentioned on the website:
"Due to limitations of macOS, blocking via host name is only applicable to (as
Apple notes) "Network.framework or NSURLSession connections".
As such, for browsers (such as Chrome), that do not leverage these frameworks,
only ip address based blocking is supported."
So what this means is that you will often get LuLu popups for connections with
only IPs specified. This isn't ideal because the endpoints being connected to
will often change IPs so if you simply add rules based on the IPs you'll be
forever plagued with popups whenever they change, and so because of this the
tendency is to simply allow *:443 or similar to avoid endless popups. This is
bad because you're granting more egress to the application than would
otherwise be ideal.
There is however a way you can make this a lot letter and that's by using an
http proxy to filtering the outbound requests. One such proxy product is Squid
which is freely available via Homebrew or Macports. If you can force
applications to connect through squid instead of directly you can have proper
control over what hosts they can connect to using hostnames even if the
applications themselves don't use the Network or NSURLSession frameworks.
The first thing to determine is what stuff you can force over the proxy. Good
candidates for this are anything that you run from the terminal - curl,
terraform, python, anything like this can be configured to connect to an http
proxy. Most terminal apps will respect the environment variables https_proxy or
HTTPS_PROXY so these can simply be set in your bash config.
Next we need Squid installed via either brew or macports, if you're using brew
be aware that it install everything with file ownership as the local user so you
won't want launchd invoking the squid binary as root. Obviously squid will need
LuLu rules allowing it to make outbound connections, I'd suggest allowing ports
80 and 443 outbound to all addresses at a minimum.
Then we want a simple allowlist config for squid:
acl allowlist dstdom_regex '/etc/squid/allowlist.txt'
http_access allow allowlist
http_access deny all
We can put hostname regexes into allowlist.txt and these will be allowed by the
LuLu won't filter any connections to localhost so the ruleset for your
applications that can talk to the proxy can be very simple - allow DNS and
nothing else. I like to create two rules in Lulu for all applications, one to
allow DNS and one to block all other traffic. Any rules created that allow
connections will override the default block-everything rule, and this way if a
connection is denied it's simply denied - no annoying popups.
Once this is set up you can simply tail the Squid access log to see what stuff
is being connected to, anything that isn't in the allowlist.txt will return a
403 and you can edit the list and restart squid if you need to.
It's even possible to control access to github using this approach. Github is
annoying in that its IPs change almost by the second, making filtering
connections to it with LuLu practically impossible without simply allowing all
traffic on tcp/22. However using this squid approach and socat we can route all
ssh connections to github over the proxy:
ProxyCommand socat - PROXY:localhost:%h:%p,proxyport=3128
Lulu is an excellent product but the limitations of the OS hold it back, by
integrating it with a local http proxy we can get much more control over what
connections go out of the system.