了解有关获取元数据标头的所有信息,以及如何实施隔离策略来防御各种客户端攻击。
什么是获取元数据标头?
获取元数据标头是一种相对较新的浏览器安全功能,您可以在 Web 应用程序中使用它来防止前所未有的客户端攻击。
标题的目的很简单。它们告诉 Web 服务器发生 HTTP 请求的上下文。
一个例子
这是吉姆。b.example
他使用 cookie登录SessionId=123
。
假设有一个网站a.example
从b.example
.
不获取元数据标头
在获取元数据标头存在之前,当 Jim 打开时,Web 服务器所看到的a.example
只是有人使用 Jim 的 cookie 加载了图像。
在 HTTP 级别上,它可能如下所示:
GET /cat.jpg HTTP/1.1
Host: b.example
Cookie: SessionId=123
请注意,服务器无法知道是否a.example
使用b.example
Jim 的会话 ID 加载图像。
使用获取元数据标头
在支持获取元数据标头的情况下,浏览器将在请求中向 Web 服务器发送更多信息。
在 HTTP 级别上,它可能如下所示:
GET /cat.jpg HTTP/1.1
Host: b.example
Cookie: SessionId=123
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site
注意这次浏览器如何告诉网络服务器三个新的东西:
- 该请求源自图像元素。
- 请求的模式(这是一种资源负载,而不是例如导航)。
- 该请求来自另一个网站。
实际上,请求中还暗示了第四件事。缺少Sec-Fetch-User
标头意味着请求不是由于用户交互而产生的。
获取元数据标头可以防止哪些攻击?
获取元数据标头可以帮助防止几乎任何跨站点攻击。这些包括:
获取元数据标头
该系统由四个 HTTP 请求标头组成。
Sec-Fetch-Site
标Sec-Fetch-Site
头告诉网络服务器请求是来自同一来源、同一站点还是完全不同的网站。它可以具有以下值之一:
same-origin
:请求来自同一个origin,即host、port、scheme相同。你可以在这里阅读更多关于起源的含义。same-site
: 请求来自不同的来源,但来自同一站点,这是同一可注册域的子域的浏览器术语。例如:https://a.example.com
并且https://b.example.com
被认为是同一站点(但来源不同)。cross-site
: 请求完全来自不同的网站。例如,www.google.com
并且www.facebook.com
被认为是跨站点的。none
: 请求并非来自任何网站。当用户打开书签、在 URL 栏中手动键入、打开另一个程序的链接等时,就会发生这种情况。
秒取模式
Sec-Fetch-Mode
告诉网络服务器请求的模式。它可以具有以下值之一:
same-origin
:浏览器正在向同一来源发出请求。如果目标来自不同的来源,使用此模式的请求将失败。no-cors
:浏览器正在向另一个来源发出请求,但不希望读取响应或使用任何未列入安全列表的 HTTP 动词或标头。cors
:浏览器尝试发出 CORS(跨域资源共享)请求。您可以在此处阅读有关 CORS的更多信息。navigate
:浏览器正在从一个页面导航到另一个页面,例如单击链接、接收重定向或打开书签时。
Sec-Fetch-Dest
告诉网络服务器请求的Sec-Fetch-Dest
目的地,即在什么地方等待资源。在上面的例子中,它是一个<img>
加载资源的元素,所以目的地是image
.
一些可能的值是:
empty
: 当资源通过fetch()
或 XHR 加载时。document
:当资源在顶级导航中加载时。image
: 当资源被加载到<img>
标签中时。worker
: 当资源通过new Worker(...)
.iframe
: 当资源加载到<iframe>
.
该值可以是任何能够加载外部资源的元素,因此audio
, audioworklet
, embed
, font
, frame
, manifest
, object
, paintworklet
, report
, script
, serviceworker
, sharedworker
, style
, track
, video
,xslt
等也是可能的。
Sec-Fetch-User
标Sec-Fetch-User
头告诉网络服务器由于用户交互(例如通过单击链接)而发起的导航请求(理论上)。该值始终为?1
。当由于浏览器不考虑“用户激活”而导致导航发生时,根本不会发送此标头。
浏览器支持
在撰写本文时,Chrome、Edge 和 Opera 支持获取元数据标头。但不用担心,您可以以完全向后兼容的方式实施策略。您可以在此处查看最新状态。
实施隔离政策
获取元数据标头如此出色的原因是它们允许我们这样做。
根据客户端上下文阻止服务器端的请求是我们以前从未有过的能力。但现在我们这样做了,所以让我们使用它并实施隔离策略。
要创建隔离策略,您需要一个过滤器、中间件等,使您能够根据 HTTP 请求标头阻止请求。
我将使用 NodeJS 作为示例,但是在任何开发框架中实现这样的中间件通常都是微不足道的。
我们将从这个骨架开始:
app.use(function (req, res, next) {})
1. 向后兼容
通过允许根本没有获取元数据标头的请求开始您的策略。否则,使用尚不支持获取元数据的浏览器的人将无法访问您的网站。
if (!req.headers['sec-fetch-site']) {
return next()
}
if (!req.headers['sec-fetch-mode']) {
return next()
}
if (!req.headers['sec-fetch-dest']) {
return next()
}
2.允许所有来自同一来源的请求
要使您的应用程序正常工作,请允许来自同一来源的所有交互。
if (req.headers['sec-fetch-site'] === 'same-origin') {
return next()
}
3. 允许不是来自其他网站的请求
有时请求来自用户打开书签或在 URL 栏中输入。在这些情况下, 的值Sec-Fetch-Site
是none
,所以让我们允许它。
if (req.headers['sec-fetch-site'] === 'none') {
return next()
}
4. 允许导航
要使其他网站能够链接到您的页面,必须允许导航。但是,仅允许导航也将允许跨站点 POST 请求、框架和其他我们不一定想要的东西。
为安全起见,请验证 HTTP 方法是否为GET
并且资源目标是否为document
.
if (
req.method === 'GET' &&
req.headers['sec-fetch-mode'] === 'navigate' &&
req.headers['sec-fetch-dest'] === 'document'
) {
return next()
}
5.阻止任何其他请求
如果一个请求到目前为止不符合任何规则,则拒绝它。
return res.status(403).json({
error: 'Request blocked by the isolation policy.',
})
6. 必要时放宽政策
您可能希望允许一些跨域或跨站点交互。
允许子域
要允许来自您自己的子域的请求,请允许具有Sec-Fetch-Site
值的请求通过same-site
。
if (req.headers['sec-fetch-site'] === 'same-site') {
return next()
}
允许取景
要使其他网站能够在 iframe 中加载您的页面,GET
请允许在目标为iframe
.
if (
req.method === 'GET' &&
req.headers['sec-fetch-mode'] === 'navigate' &&
req.headers['sec-fetch-dest'] === 'iframe'
) {
return next()
}
7. 测试策略
我们现在实施了相当严格的隔离政策。这是全部内容(在我的示例中,我不允许 iframe 或子域)。你可以在这里fork 和玩代码。
app.use(function (req, res, next) {
// If fetch metadata is not supported, allow the request.
if (!req.headers['sec-fetch-site']) {
return next()
}
if (!req.headers['sec-fetch-mode']) {
return next()
}
if (!req.headers['sec-fetch-dest']) {
return next()
}
// If the request originates from your own web application, allow it.
if (req.headers['sec-fetch-site'] === 'same-origin') {
return next()
}
// If the request doesn't originate from a website at all (bookmark, etc.) then allow it.
if (req.headers['sec-fetch-site'] === 'none') {
return next()
}
// If the request is a navigation GET request, allow it.
if (
req.method === 'GET' &&
req.headers['sec-fetch-mode'] === 'navigate' &&
req.headers['sec-fetch-dest'] === 'document'
) {
return next()
}
// If no rules matched, block the request.
return res.status(403).json({
error: 'Request blocked by the isolation policy.',
})
})
我在这里创建了一个小测试页。它尝试加载图像、发布 HTML 表单并构建受保护的网站。如果你打开网站,你会发现这三个都失败了。
还有一个链接。单击链接,页面正确加载。
此外,在 Firefox 或 Safari 中加载页面工作正常,因此我们的向后兼容性似乎得到了检查。
8. 部署策略
首次将策略部署到生产环境时,建议使用仅记录日志的方法。
该策略将保持不变,但不是阻止请求,而是记录一个请求会因为 X 而被阻止,然后让该请求通过。
这样,您将很快知道策略是否会破坏某些内容,以及在最终部署强制执行策略之前是否需要添加一些内容。
结论
获取元数据标头是一项出色的浏览器功能,它使开发人员能够以前所未有的方式保护 Web 应用程序。
实施有效的策略很简单,并且可以轻松地放宽以适应任何特殊需求,例如允许框架或来自子域的交互。
将策略部署到生产环境时,建议从仅记录方法开始。然后,当您对它不会破坏任何内容感到满意时,稍后部署执行策略。
根据浏览器的不同,浏览器支持仍然缺乏或处于试验阶段,但标头已经可以向后兼容的方式使用。