使用 PF 在 MacOS 上禁用特定端口

Tech  2022年5月15日  22:21

前情提要

四月份的某天,意外发现当月的机场流量烧得异常的快,一番定位发现原来是舍友手机上的 Clash for Android 在高强度烧流量。当时一直在怀疑手机里有什么木马程序在使用 Clash 进行代理,结果最后发现其实是 Clash 自己的锅。简单来说就是,Clash 自己打开了“Allow LAN”设置,允许外部来的连接。这个功能设计之初的目的是为了:你可以在一台设备上运行 Clash,然后在其他设备上连接这台设备的代理端口来使用代理。但是清华大学的校园网分配的是公网 ip,也就是说你的代理端口直接暴露到公网上了。有心人可以通过在公网上扫描 7890 端口来发现你的 Clash,然后利用你的 Clash 作为代理去访问一些网站,把你的设备变成 DDoS 肉鸡。关于这件事情,可以从下面这篇文章里看到舍友视角:关于配置不安全使我的手机被用于 DDOS 这件事

因为知道会有这种情况存在,所以后续一直有在关注我自己的 Clash。然后我就发现,我的 ClashX(一个轻量级的 MacOS 上的 Clash)居然也打开着“Allow LAN”。然后检查过去几天的网络流量曲线,发现果然也中招了,只能说幸好电脑开着的时间没有手机那么长。再后来,听说有不少人因为这个漏洞导致的异常流量被 its 发现,校园网账号被封,还收到警告邮件要求卸载 socks5 代理软件……想想就后怕。

本以为这事应该彻底翻篇了,直到后来有一天无意中瞥了眼状态栏,发现莫名其妙有 2MB/s 的上下行速度,立刻打开 ClashX 检查发现果然“Allow LAN”又被打开了。我明确记得上次绝对已经把它关上了,它怎么还能自己重新打开的?但我懒得多想,只是重新把它关上,结果后来这种事情又反反复复地出现。最后我发现,如果重启了 ClashX(或者重启了电脑),它就会默认把这个设置重新打开。虽然很烦人但一时也没什么办法,我就只能一旦发现就把它修正过来,直到今天,我终于下定决心要和这玩意儿做个了断。

愚蠢的 ClashX

我第一反应就是找到 ClashX 具体是如何配置“Allow LAN”的。我猜想它应该写在某个配置文件里,只要把配置文件 overwrite 掉,ClashX 启动读取配置文件时就会自动把这个设置关掉。又或者,我们改变代理端口,这样当有人在公网上扫描 7890 端口时也找不到我们的机器。然而经过查找,我发现——这些配置居然是写在策略组的配置文件里的……也就是说本地的“Allow LAN”、代理端口这些配置,居然是和远端的节点列表、分流策略这些东西一起指定的。看到这我直接凌乱了……这两部分一个本地一个远端,看上去就不该耦合在一起的东西为什么会耦合在一起啊……这下直接修改配置文件也没用了,因为这个配置文件会通过订阅链接自动更新,你就算把它改了,下次一更新,这些配置又变回去了。

我猜想 ClashX 有一些能够 override 这些配置的方法;或者说它 理应有 能够 override 这些配置的方法,但实际上没有。ClashX 是个开源软件,我找到了它的 Github 仓库,想看看能不能通过提 issue 的方法来修复这个问题,结果发现一月份的时候就有人提 issue 想要一个配置预处理的功能,然而根本没有得到回复。舍友说大概是这个讨论区充斥着大量很蠢的问题,所以 maintainer 都已经懒得看了。我觉得这个问题还挺严重的(毕竟可以说涉及到软件的安全性),所以想要直接给仓库的 owner 发邮件,结果发现人家根本没有留下联系方式。我还把仓库 clone 下来看了看 git log,发现他用的是 github 的 noreply 邮箱。看来他为了不被查水表也是做了不少隐藏自身的努力。

ClashX 还留下了一个 telegram 讨论群,进去以后并不想直接在大群里发消息(被淹没的概率太大了),打算直接找到 maintainer 跟他私聊,结果翻到他的账号,info 写着:私聊不回。得,所有路径都被封死,看来只能考虑自己改代码提 PR 了,但是 ClashX 79.8% 由 Swift 写成,很不熟悉,况且为了防止查水表,估计还得弄个小号来 contribute。我本意只是想解决自己的问题罢了,想了想,既然 maintainer 自己都这么遗世独立,我也没必要费心费力做什么贡献了,另寻他路便是。

愚蠢的 MacOS 防火墙

舍友指了条明路,就是防火墙。如果能使用类似 iptable 那种防火墙,直接把 7890 端口禁掉就没那么多事了。想法很美好,但是 MacOS 并没有 iptable;打开系统设置里的防火墙,发现这个防火墙的设计居然是针对应用的——就是说你可以禁止掉一些应用的出/入流量,但并找不到针对端口的设置方式。而且更蠢的是,禁掉 ClashX 的入流量看上去并没有用:一旦打开“Allow LAN”,上下行流量马上开始异常飙升。我实在是没想到 MacOS 给防火墙设计了一个这么愚蠢的交互逻辑……

但也并不是完全没办法针对端口来操作防火墙。MacOS 毕竟是一个 UNIX-like 的操作系统,通过 shell 我们可以做到不少事情。经过一番查找,我发现可以通过 PF(Packet Filter)来实现。我首先找到了一个两年前的问答帖:How can I block all ports except 443 on macOS Catalina,发现了 PF 这个工具的存在。不过这个人想实现的 block 效果和我可谓是背道而驰,他是白名单而我是黑名单。但希望的曙光已经出现,我预感到只要合理地编写配置文件,一定可以实现我想要的效果。我先是照着 PF Manual Page 上的说明自己瞎操作,但最终的结果都是把我的整个网络给搞崩。经过不懈努力,最终我找到了这个:Firewall setting notes using PFCTL not IPFW on OS X。那一刻,我感觉到了什么叫“山重水复疑无路,柳暗花明又一村”。

很难想象,在 MacOS 上实现一个稀松平常的禁用端口的操作,居然要经过这么“艰苦卓绝”的努力。至此,整件事情终于告一段落。由于我觉得 block 一个指定端口可能是个很常用的功能,所以也记录在下面,方便以后查阅。

总结

首先,最好不要直接修改 /etc/pf.conf,以防止把网络设置搞崩(刚开始尝试的时候,由于错误地编写了配置文件,我每次激活配置都会导致电脑彻底断网……拥有一个 reset 手段是非常重要的)。

创建一个新的 conf 文件,保存到任何你喜欢的位置。在其中写入:

block_ports = "{7890, 7891}"

block drop in proto tcp from any to any port $block_ports

pass in quick on lo0 proto tcp from any to any port $block_ports

它的含义大致是:我们把 7890 和 7891 端口上的任何 tcp 连接都 block 掉了,并且会直接把 packet 给 drop 掉。然而,我们额外允许了来自本机(localhost)的连接,因为我们可能会需要为一些软件配置 proxy,这时候会使用 localhost:7890 来代理。

保存文件后(不妨假设文件名是 mypf.conf),可以使用这条命令来检查语法:

sudo pfctl -nf mypf.conf

如果没有看到“Syntax Error”则说明语法无误。激活配置使用 -e 参数( -f 参数是从文件读取的意思):

sudo pfctl -ef mypf.conf

我们也需要一个撤销手段。使用 -d 参数可以 disable 掉这个配置:

sudo pfctl -df mypf.conf

使用 telnet 工具可以确认:从本机可以正常连上 7890 和 7891 端口,但从其他机器上则无法连接。如果 disable 这个配置,则其他机器上也可以正常连接。It works perfectly!

若想要开机时自动应用这些配置,可以尝试使用 这里 的方法。