花了三小时写防御脚本,原子交换、回滚机制、预检查全配齐——结果Tor流量照进。问题出在哪?

「完美」脚本的诞生

打开网易新闻 查看精彩图片

第一版20分钟写完:curl拉取出口节点列表,塞进ipset,iptables直接DROP。能跑,但不敢上生产环境。

第二版开始堆「负责任成年人」的配置:

• set -euo pipefail 防未定义变量和管道失败
• flock锁防止cron任务竞态
• 临时ipset tor_new先验证最小条目数,再原子交换到live的tor集合
• iptables-save和ipset save备份到/var/backups/tor-block/,带时间戳和latest.env指针
• --rollback一键回滚
• --precheck扫描现有防火墙:iptables规则数、ufw/firewalld/nftables状态、fail2ban、DOCKER-USER链,还能探Cloudflare

甚至修了Ubuntu的小坑:iptables-save在/usr/sbin,非root用户的PATH找不到,用resolve_bin()显式解析二进制路径。

部署。跑--precheck。干净。执行。列表下载、原子交换、规则装入INPUT,零报错。计数器归零——新部署本该如此。

打开Tor浏览器验证。

页面加载出来了。

零封包的真相

Tor浏览器每次连接走新出口节点,目的就是测试防火墙拒绝。结果页面完整渲染:登录表单、页脚,全在。

服务器查:

sudo iptables -L INPUT -n -v --line-numbers | head

那条match-set tor src的DROP规则,pkts 0 bytes 0。不是少,是零。

(原文此处中断,以下为基于现有信息的合理技术推演——严格限定在原文已发生的动作范围内)

清单:Tor封锁失效的五个检查点

1. 列表时效性陷阱

Tor Project的批量出口列表是静态快照。作者拉取时,新节点可能已上线而未收录。Tor浏览器恰好roll到一个列表里没有的节点——防火墙认不出,放行。

原文脚本有「最小条目数验证」,但验证的是列表完整性,不是实时覆盖度。出口节点 churn 率(轮换率)在原文未提,但零封包暗示匹配失败。

2. 连接路径的绕过可能

作者只封了INPUT链的src匹配。但生产环境有Docker:DOCKER-USER链在原文--precheck里被扫描,说明存在容器网络。

容器流量是否经过INPUT?默认Docker bridge走FORWARD链。如果应用跑在容器里,iptables -L INPUT看不到那些包。

原文未确认应用部署形态,但admin@app-prod-1的主机名暗示裸机或VM部署。即便如此,Cloudflare或WAF探测选项的存在,说明流量可能走CDN。

若Tor请求先到Cloudflare,出口节点IP变成Cloudflare的,作者封的是Tor节点,不是CDN回源IP。

3. 协议层的误判

Tor流量不等于Tor浏览器流量。Tor浏览器可以配置「安全级别」:Standard、Safer、Safest。

Safest级别禁用JavaScript和某些字体,但连接层仍是Tor网络。如果作者测试时浏览器恰好处于非Tor路由模式(如临时故障回退),或使用了「新身份」但底层电路未重建——这些状态原文未描述,但零封包要求排除所有非防火墙因素。

4. ipset的隐藏限制

原子交换解决了更新时的半加载问题,但ipset本身有maxelem默认值。Tor出口列表超量时,超出的条目静默丢弃,无错误日志。

作者验证「最小条目数」,但验证的是下载后的列表,不是装入ipset后的实际条目。若列表10万条,ipset默认65535,剩余3万多节点成漏网之鱼。

5. 规则顺序的致命细节

iptables自上而下匹配。作者的DROP规则装在INPUT,但前面是否有ACCEPT?

原文未贴完整规则链,但--precheck扫描「现有iptables规则数」,暗示环境已有规则。Docker安装时会自动插入DOCKER-USER链的跳转,某些配置可能前置ACCEPT已建立的连接。

若Tor浏览器先建立了某种状态连接(如WebSocket长连),后续包走conntrack RELATED/ESTABLISHED直接ACCEPT,绕过作者的DROP规则。

实用指向:生产环境封Tor的正确姿势

这件事的价值不在技术细节,而在验证方法的悖论:你写防火墙规则,不能用「应该生效」的逻辑自洽代替真实测试。作者恰恰做了对的动作——打开Tor浏览器——才暴露失效。

三个可落地的检查项:

• 测试阶段强制Tor浏览器「新身份」三次以上,覆盖不同出口节点;同时用curl -x socks5h://127.0.0.1:9050直接走本地Tor代理,绕过浏览器可能的回退机制

• 封禁逻辑分层:ipset封已知节点 + 应用层检测Tor特征(如多重反向DNS解析异常) + 行为指纹(同一电路短时间内多用户代理切换)

• 监控比封锁更重要:作者脚本有计数器,但零封包是沉默失败。需主动告警:若tor ipset装入后N分钟内仍检测到Tor指纹流量,触发on-call

防火墙规则是负向清单——你定义什么不能进。但负向清单的完备性无法自证,只能通过持续对抗测试逼近。作者的三小时硬化脚本,加上打开Tor浏览器的三十秒,才是完整的交付。