大家好,我是 世奇,笔名 ConardLi

在刚刚发布的 Chrome 98 里面,有这样一项更新:

Chrome 将在任何对子资源的私有网络请求之前开始发送 CORS 预检请求,该请求需要目标服务器的明确许可。

啥?CORS 不是用来解决跨域的吗,跟私有网络有啥关系?啥是私有网络请求?

能问出这俩问题,一定没好好看我的公众号,其实之前在多篇文章里都提到过相关的策略解读,

Chrome 98 这个版本,对私有网络的限制正式生效啦,主要目的是保护用户免受针对私有网络上的路由器和其他设备的 CSRF 攻击。攻击者可以借助这个攻击方式将他们重定向到恶意服务器。

私有网络

私有网络请求指的是目标服务器的 IP 地址比请求发起者获取的 IP 地址更私密的请求。例如,从公共网站 (https://example.com) 到私人网站 (http://router.local) 的请求,或从私人网站到 localhost 的请求。

专用网络访问(以前称为CORS-RFC1918)会限制网站向私有网络上的服务器发送请求的能力。

Chrome 已经实现了部分规范:从 Chrome 96 开始,只允许安全上下文发出私有网络请求。

预检请求

预检请求是跨域资源共享(CORS)标准引入的一种机制,用于在向目标网站发送可能有副作用的 HTTP 请求之前先向其请求一个许可。这确保了目标服务器理解 CORS 协议并显着降低了 CSRF 攻击的风险。

权限请求会作为 OPTIONS HTTP 请求发送,带有描述即将到来的 HTTP 请求的特定 CORS 请求标头(比如:Access-Control-Request-Method)。响应也必须携带明确同意即将到来的请求的特定 CORS 响应标头(比如:Access-Control-Allow-Origin)。

CORS 预检新增的两个 Header

为了限制私有网络请求,新增了两个 CORS 预检 Header

  • Access-Control-Request-Private-Network: true 在所有私有网络预检请求上设置
  • Access-Control-Allow-Private-Network: true 必须在所有私有网络预检响应上设置

注意:无论请求方法和模式如何,都会为所有私有网络请求发送预检请求。这个请求在 cors 模式以及 no-cors 所有其他模式中的请求之前就已经发送了。

如果目标 IP 地址比发起请求的网址更私密,私有网络的预检请求也会针对同源请求发送。这和我们理解的常规 CORS 不一样,其中预检请求只会用于跨域请求。同源请求的预检请求还可防止 DNS 重新绑定攻击。

一个例子

在 NO-CORS 模式下

假设我们在 https://foo.example/index.html 嵌入了 <img src="https://conardli.example/cat.gif" alt="dancing cat"/> 并且, conardli.example 会解析为 私有 IP 地址 192.168.1.1

Chrome 首先会发送一个预检请求:

1
2
3
HTTP/1.1 OPTIONS /cat.gif
Origin: https://foo.example
Access-Control-Request-Private-Network: true

想要让这个请求成功,服务器必须响应:

1
2
3
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Private-Network: true

如果你设置了 Access-Control-Allow-Origin: *,也是可以请求成功的,不过这个风险较大,不建议设置。

在 CORS 模式下

假如我们在 https://foo.example/index.html 运行了下面的代码:

1
2
3
4
await fetch('https://conardli.example/delete-everything', {
method: 'PUT',
credentials: 'include',
})

conardli.example 假如被解析为 192.168.1.1

Chrome 首先发送一个预检请求:

1
2
3
4
5
HTTP/1.1 OPTIONS /delete-everything
Origin: https://foo.example
Access-Control-Request-Method: PUT
Access-Control-Request-Credentials: true
Access-Control-Request-Private-Network: true

想要让这个请求成功,服务器必须响应:

1
2
3
4
5
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Credentials: true
Access-Control-Allow-Private-Network: true

然后 Chrome 将发送实际请求:

1
2
HTTP/1.1 PUT /delete-everything
Origin: https://foo.example

服务器可以按照正常的 CORS 规则对它进行响应:

1
2
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://foo.example

怎么知道我的网站会不会受影响

Chrome 98 开始,如上面我们介绍的预检请求失败,请求依然会成功,但会在 DevTools 问题面板中显示一个警告。

受影响的预检请求也可以在 Network 面板中查看得到:

如果你想查看一下强制执行预检成功会发生什么,你可以改一下下面的命令行参数(从 Chrome 98 开始):

1
--enable-features=PrivateNetworkAccessRespectPreflightResults

具体的实施计划

Chrome 98 中:

  • Chrome 在私有网络子资源请求之前发送预检请求。
  • 预检失败仅在 DevTools 中显示警告,不会影响私有网络请求。
  • Chrome 会收集兼容性数据并联系受影响最大的网站。
  • 希望在这期间现有网站能得到广泛兼容。

最早在 Chrome 101 中:

  • 只有兼容性数据表明这个更改不会产生太大的影响并且我们在必要时才会开始。
  • Chrome 强制要求预检请求必须成功,否则请求失败。
  • 弃用试验同时开始,以允许受此阶段影响的网站请求延长时间,试验将持续至少 6 个月。

想了解更多细节,请查看 Chrome 官方博客:https://developer.chrome.com/blog/private-network-access-preflight/

如果你想加入高质量前端交流群,或者你有任何其他事情想和我交流也可以添加我的个人微信 ConardLi