你以为 WebSocket 只能靠后端做地址检查?今天 Edge 154 用一个构造函数选项把这件事前端化了

你写了一个本地 WebSocket 服务,在开发环境跑得飞起,上线后却被告知”这个域名指向了内网 IP,不允许跨域“——或者反过来,测试环境一切正常,生产环境被人用 DNS 反弹绑到了你的内网服务上。今天 Edge 154 给 WebSocket 加了一个构造函数选项,把这件事从根上堵住了。

这个坑是怎么来的

WebSocket 建立连接的时候,浏览器会先做一个 HTTP 握手,握手请求里的 Host 头就是服务器告诉浏览器的”我是谁”。这个机制本身没问题,但 DNS 可以在中间做一件事:把 ws://dev-api.example.com 解析到 127.0.0.1,让浏览器以为自己连的是外网服务,实际上连的是攻击者机器上的本地服务——这就是 DNS 反弹攻击。

以前防这个,只能靠服务器自己在握手阶段检查 OriginHost 头,或者依赖反向代理做网络层隔离。客户端这边,没有办法在连接建立之前就告诉浏览器”我只接受特定地址空间的服务器”。

targetAddressSpace 是什么

Edge 154 在 WebSocket 构造函数里新增了一个 targetAddressSpace 选项,可以指定连接必须满足的地址空间要求:

const ws = new WebSocket('wss://api.example.com/socket', {
  targetAddressSpace: 'private'
});

可选值有三个:

  • public:服务器必须是公网可路由的 IP
  • private:服务器可以是私有地址(RFC 1918,如 192.168.x.x10.x.x.x172.16.x.x~172.31.x.x)或公网地址
  • local:服务器必须是本机地址(127.0.0.0/8::1

浏览器在建立连接前会先做 DNS 解析,然后根据解析结果检查地址空间是否满足要求。如果不满足,WebSocket 直接拒绝连接,根本不会发出握手请求。

实际场景怎么用

最典型的场景是本地开发服务器。很多团队在本地跑一个 WebSocket 热更新服务,或者接一个本地的状态同步服务:

// 开发环境连本地服务,明确要求 local 级别
const localWs = new WebSocket('ws://localhost:8080/ws', {
  targetAddressSpace: 'local'
});

上了生产或者 staging 环境,地址换成内网域名或者内网 IP,同一段代码保持 targetAddressSpace: 'private' 不变,浏览器会自己根据实际解析结果判断是否放行。

反过来,如果你接的是外部第三方服务,明确要求 public 可以防止 SDK 或者中间件错误地把连接指向了一个内网地址——这在微服务架构里尤其有用,服务之间的内部通信如果误用了外部域名解析,风险就不只是开发环境的问题了。

现在的支持情况

这个 API 是 Edge 154 首发,Chrome 和 Firefox 目前还没有支持。具体兼容性可以在 chromestatus.com 上查,关键词是 targetAddressSpace option for WebSocket

对于还没支持的浏览器,有两个思路:一是 feature detection,不支持就不填这个选项,继续靠服务端检查;二是如果有条件编译能力,在构建阶段给不同浏览器打不同的 WebSocket 调用代码。

下一步

如果你现在就在维护一个 WebSocket 服务,尤其是涉及内网穿透、DNS 动态绑定的场景,把这个选项加进去成本很低——加一个选项,掉一个攻击向量。如果你是框架或者 SDK 的作者,也建议把这个默认值考虑进去,默认 private 比默认不限制要安全得多。

地址空间检查这件事,终于不用全压在服务端了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 13 阅读