工程文章

云端 Mac 监听端口审计:用 lsof、pfctl 与 SSH 收紧暴露面

云端 Mac 监听端口审计:用 lsof、pfctl 与 SSH 收紧暴露面

远程 Mac 上跑过几轮构建后,开发工具、调试服务器、包管理器和临时脚本可能留下监听端口。真正需要处理的不是“端口数量多”,而是无法回答三个问题:谁在监听、绑定到哪个地址、机器重启后会不会再次出现。下面用 macOS 自带工具建立一套可重复的只读审计流程,再逐步缩小暴露面。

先建立监听端口基线

先在节点空闲、没有临时调试任务时采集 TCP 和 UDP 状态。-nP 可以避免名称解析和端口名称转换,输出更适合保存与比较。

mkdir -p "$HOME/audit/network"
stamp="$(date -u +%Y%m%dT%H%M%SZ)"

sudo lsof -nP -iTCP -sTCP:LISTEN \
  > "$HOME/audit/network/tcp-$stamp.txt"

sudo lsof -nP -iUDP \
  > "$HOME/audit/network/udp-$stamp.txt"

netstat -anv -p tcp \
  > "$HOME/audit/network/netstat-tcp-$stamp.txt"

不要只看端口号。COMMANDPIDUSERNAME 必须放在一起判断:同一个开发服务器以普通用户运行,与未知程序以 root 运行,处置优先级完全不同。

监听形式 通常含义 下一步
127.0.0.1:8080 仅 IPv4 本机访问 确认是否为预期开发服务
[::1]:8080 仅 IPv6 本机访问 保留并测试 SSH 转发
*:8080 监听所有可用接口 核对进程并优先收紧
10.x.x.x:8080 绑定具体接口 结合节点网络策略验证
UDP 无固定对端 可能是发现或辅助服务 查询进程用途与启动来源

LISTEN 不等于外部一定可达。监听地址、主机防火墙和节点网络策略共同决定最终结果,但通配地址意味着进程主动扩大了接收范围,应先解释清楚。

把端口追到进程与启动项

找到 PID 后,用 ps 确认完整命令、父进程和运行时长,再检查它是否来自常驻启动项。

pid=1234
ps -p "$pid" -o pid,ppid,user,lstart,command
sudo lsof -nP -p "$pid"

launchctl print "gui/$(id -u)" > "$HOME/audit/network/launch-gui.txt"
sudo launchctl print system > "$HOME/audit/network/launch-system.txt"

find "$HOME/Library/LaunchAgents" \
  /Library/LaunchAgents \
  /Library/LaunchDaemons \
  -maxdepth 1 -name '*.plist' -print 2>/dev/null

对可疑 plist 使用 plutil -p 文件路径 查看 ProgramProgramArgumentsRunAtLoadKeepAlive。如果进程由构建脚本临时启动,任务结束后应退出;如果 KeepAlive 让它不断重启,仅仅执行 kill 不能解决问题。

建立处置清单

每条非系统监听至少记录服务用途、负责人、启动方式、绑定地址和保留理由。无法归属的进程先停止相关任务并保存命令行、日志路径和 plist 内容,不要立即删除文件,否则会丢失排查上下文。

优先改绑定地址而不是先封端口

开发服务若只供当前用户使用,最直接的修正是绑定到回环地址。这样即使防火墙规则变化,进程也不会主动接受其他接口的连接。

python3 -m http.server 8080 --bind 127.0.0.1

HOST=127.0.0.1 PORT=8080 ./run-development-server

从自己的电脑访问时,用 SSH 本地转发承接入口:

ssh -N \
  -L 127.0.0.1:9000:127.0.0.1:8080 \
  "$REMOTE_USER@$REMOTE_HOST"

随后访问本地 127.0.0.1:9000。应同时验证远端服务仍只监听 127.0.0.1:8080,而不是因为启动参数未生效退回 *:8080

收紧绑定地址通常比新增阻断规则更容易验证,也更不容易误伤远程管理连接。防火墙是第二层控制,不应替代正确的服务监听配置。

只读核对两层防火墙

macOS 的应用防火墙与 pf 关注点不同。审计时先读取状态,不要在远程会话里直接覆盖规则。

sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --listapps

sudo pfctl -s info
sudo pfctl -sr
sudo pfctl -sn

应用防火墙列表用于确认哪些程序被允许接收入站连接;pf 输出则用于查看过滤和地址转换规则。发现异常时,先保存完整输出,再确认规则来自系统配置、运维流程还是临时实验。

远程修改 pf 最大的风险不是语法错误,而是把当前管理连接一起阻断。没有第二条已验证的连接路径时,应停留在只读检查。确需变更,也应先备份规则、明确回滚命令,并在另一个会话中持续确认控制台可达。

用差分检查防止端口回潮

一次清理不能覆盖后续工具升级、构建脚本变更和新启动项。建议在环境初始化完成后保存认可的基线,并在每次较大变更后重新采集。

latest_tcp="$(ls -t "$HOME"/audit/network/tcp-*.txt | head -n 1)"
diff -u "$HOME/audit/network/tcp-approved.txt" "$latest_tcp"

latest_udp="$(ls -t "$HOME"/audit/network/udp-*.txt | head -n 1)"
diff -u "$HOME/audit/network/udp-approved.txt" "$latest_udp"

差分出现新增项时,按固定顺序检查:确认进程归属,确认监听地址,确认启动来源,判断是否需要远程访问,最后才决定保留、改成本机绑定或停用。不要把端口号加入静态白名单后就停止追踪,因为同一端口可能由不同程序接管。

在 OwnAMac 的独享物理节点上,这套流程同样适用于构建代理、预览服务器、测试回调进程和临时文件服务。完成后保留原始快照、处置记录与复查结果,下一次环境变更就能从差异开始,而不是重新猜测当前状态。

常见问题

看到端口处于 LISTEN 状态,就代表它能从公网访问吗?

不一定。还要检查监听地址、节点网络策略和防火墙规则。绑定在 127.0.0.1 或 ::1 的服务通常只能由本机访问,而绑定在 * 或具体网卡地址上的服务才需要继续验证外部可达性。

远程审计时可以直接修改 pf 规则吗?

不建议在没有备用连接路径时直接改写规则。应先保存当前规则和监听快照,确认控制台仍可访问,再在独立会话中进行小范围验证;无法确认影响时,只做只读审计。

开发服务只绑定本地地址后,怎样从自己的电脑访问?

使用 SSH 本地端口转发,把本地端口映射到远端的 127.0.0.1 服务端口。这样开发服务无需监听所有网卡,访问入口仍由已有的 SSH 认证链路控制。

OwnAMac 云端 Mac

把下一次构建放到独享 Apple Silicon 物理节点

按任务规模选择三档配置与五个节点,计算和存储不与其他租户共享。所有节点全年 365 天正常运行,实际可用状态以控制台实时返回为准。

选择配置并订购