Junie's Blog

电脑开了代理工具,爬虫安全检查误把正常网站当内网的排查

全文共 1984预计阅读 7 分钟

在 Auto Email Sender 的智能抓取模块里,有一道非常严肃的安全红线:防范 SSRF(服务端请求伪造)

由于抓取目标往往包含大模型从网页里自主推荐出来的链接,我们必须严密防范恶意网页把爬虫诱导去内网:比如悄悄探测用户的 127.0.0.1:8080、读取内网路由器配置、或者扫描公司局域网里的 NAS。

为此,系统设置了严密的出站检查: 每一次发起 HTTP 请求前,先对域名做 DNS 解析,严厉封杀任何指向私有 IP(如 10.0.0.0/8192.168.0.0/16127.0.0.1)的请求;即便是发生了 302 重定向,对最终落地 IP 也要再过一遍安检。

逻辑很标准,直到某天一位同学提了一个匪夷所思的 Bug: 他在软件里输入了 27 个正规 985 高校的教师主页,任务刚一启动,控制台立刻红了一大片:

“请求被拒绝:URL 不允许指向本机、内网或不可解析地址。”

整整 27 位清清白白的教授官网,连一个字节都没抓下来,全被安全检查当成“黑客攻击内网”给枪毙了!

什么是 SSRF 与私有保留网段?

SSRF(Server-Side Request Forgery)是一种常见的安全漏洞。攻击者诱导服务端向内网地址发起请求,从而绕过防火墙刺探内网敏感资产。因此生产级网络库通常会拦截包括 10.0.0.0/8172.16.0.0/12192.168.0.0/16 以及 127.0.0.1 在内的所有私有保留地址。

凶手竟是 Clash 的 Fake-IP

在普通浏览器里打开这几个网址,秒级加载,没有任何问题。

我顺着代码在本地跑单元测试,手动用 Python 的 socket.getaddrinfo() 解析出问题的大学域名。

控制台打印出来的 IP 让我倒吸一口凉气:198.18.0.23

这个冷门的 IP 段触发了所有安全拦截规则!

答案呼之欲出:用户的电脑上开着代理软件(如 Clash / Mihomo),并且开启了“增强模式/系统代理”的 Fake-IP 机制!

通俗比喻:去游乐园玩项目的“虚拟排队手环”

为什么代理软件会吐出一个根本不存在的 198.18.x.x 假地址? 想象你去热门主题乐园:

  • 常规流程:你必须亲自跑到过山车门口,问清楚今天到底开不开、真实门牌号是多少(向 DNS 服务器解析真实公网 IP);
  • Fake-IP 模式:乐园门口的接待员(代理核心)直接塞给你一个写着 023 号 的手环(虚拟 IP),告诉你“不用管里面在哪,你想玩就拿着 023 号手环走专用通道,我自然会带你过去”。

对浏览器来说,只要能玩上项目,手环是几号根本无所谓; 但对我们系统的“安全安保”(SSRF 防御)来说,它只认国家正规门牌号。看到这个连公网地图上都搜不到的“手环编号”,安保人员立刻警铃大作:“可疑人员携带伪造证件,当场拿下!”

什么是代理的 Fake-IP 模式?

传统的代理转发需要先把域名送给本地 DNS 解析,再连接真实公网 IP。 而在 Fake-IP 模式下,为了加速网络握手和防止 DNS 污染,本地代理驱动会拦截所有 DNS 请求,并立刻从保留网段(RFC 2544 规定的基准测试保留段 198.18.0.0/15)里凭空捏造一个虚拟的 IP 返回给操作系统。 应用程序拿到这个假 IP 发起 TCP 连接,流量被系统驱动透明劫持到代理核心,代理核心再在远端根据原始域名去连真实的服务器。

从操作系统的角度看,这只是代理软件玩的一个“戏法”; 但从严格的 SSRF 安全防御模块看来:好家伙,198.18.* 根本不是合法的公网可路由 IP,而且归属于保留私有段,直接一枪打死!

“直接放行”是危险的自杀

查明真相后,最省事的改法呼之欲出: “既然是代理搞的鬼,那我们在校验规则里把 198.18.0.0/15 也加进白名单,或者检测到系统挂了代理就直接关闭 SSRF 检查不就结了?”

这种想法极其危险。

因为攻击者完全可以在网页里恶意构造一个解析到 127.0.0.1 的域名。如果因为开启了代理就无脑信任所有流量,恶意页面依然可以借刀杀人,通过爬虫刺探用户的内网本地服务,抓到的内网数据甚至还会被送给大模型进一步外泄!

安全防线绝不能退让。

真正需要鉴别的本质事实是:

  • 事实 A: 该域名在客观世界里确实是个公开合法的公网网站,只是在本地被代理劫持成了 Fake-IP;
  • 事实 B: 该域名本身就是一个指向私有局域网或回环网卡的恶意地址。

为此,我们重构了防御算法的鉴权流水线:

1. 发起校验 ──> 系统默认 DNS 解析 (获取 IP_1)
2. 命中 127.0.0.1 / 192.168.* ──> 立即斩杀!
3. IP_1 处于 198.18.* (疑似 Fake-IP 区域) ──> 触发【可信公网 DNS 交叉复核】
   └──> 通过独立的 223.5.5.5 / 119.29.29.29 直连公网权威解析 (获取 IP_2)
4. 若公网复核确认其为合法公网 IP ──> 判定为正常代理流量,放行!
5. 若公网复核依然为私网或解析失败 ──> 立即斩杀!
6. 发生 302 重定向 ──> 对跳转后地址严格重跑上述 1~5 步!
交叉复核的优雅之处

它既不搞域名白名单(免去了维护巨型学校列表的痛苦),也不在代理开启时自废武功。只有当本地 DNS 呈现出“代理特征”时,才动用独立的安全信道向公网核实真实身份。

尴尬的“只修了一半”

改完这套逻辑,本地写了单元测试,直接输入那几个教师主页,安检秒过,抓取正常。

正当我们准备庆祝时,新版本发出去不到两天,又有人报了同样的错: “我用的就是你们最新版本,开着代理发起抓取,怎么还是报内网拦截?!”

“不可能啊,我不是刚修过吗?”

沿着用户的点击流程一步步回放,我狠狠拍了下脑门。

原来,Auto Email Sender 的抓取流程有两个不同的入口:

  • 入口 1(个人主页入口): 直接抓某个老师的单人主页;
  • 入口 2(教师列表入口): 先抓一个长长的学院列表页,然后再抓分页,最后再抓每个人。

我上一次修复,仅仅在“入口 1”的验证函数上开启了公网复核参数! 而最核心、用户最常用的“入口 2”列表提取器,代码里竟然硬生生沿用了旧版的严格模式!

测试用例里只覆盖了单人页面抓取,自动化测试全部绿灯通过,却在最真实的业务主线路上留了个巨大的窟窿。

总结

这次排查不仅解决了 Fake-IP 与安全规则的冲突,也把整个系统的出站网络安全彻底规范化:

网络访问场景旧版行为现代化治理后
公开域名被代理映射为 Fake-IP误判为私网直接拦截经公网 DNS 复核确认后正常放行
输入明文私网 IP (如 192.168.1.1)拒绝访问严格拒绝访问
公网域名跳转到内网恶意地址存在被绕过风险重定向链路逐跳重校验并拦截
列表页、分页、正文、OCR 多入口规则割裂,各管各的全流程统一接入中央安全防护网关

现代桌面应用开发中,用户的本地网络环境千差万别(VPN、透明代理、TUN 虚拟网卡、自建 DNS 服务)。

在编写爬虫或任何具备自主外联能力的系统时:

  • 安全检查绝不能在复杂网络面前直接摆烂;
  • 用外部权威源做交叉校验,是看穿本地网络“戏法”的最佳解药;
  • 修安全漏洞时,务必拉出完整的业务链路拓扑图,切忌头痛医头、漏网大鱼。

评论