Cross-site leaks Cheat Sheet / 跨站泄露攻击技巧指南¶
Introduction / 介绍¶
This article describes examples of attacks and defenses against cross-site leaks vulnerability (XS Leaks). Since this vulnerability is based on the core mechanism of modern web browsers, it's also called a browser side-channel attack. XS-Leaks attacks seek to exploit the fact of seemingly insignificant information that is exchanged in cross-site communications between sites. This information infers answers to the previously asked questions about the victim's user account. Please take a look at the examples provided below:
本文描述了针对跨站泄露漏洞(XS Leaks)的攻击和防御示例。由于该漏洞基于现代网络浏览器的核心机制,因此也被称为浏览器端信道攻击。XS-Leaks攻击试图利用看似微不足道的信息,这些信息在网站之间的跨站通信中交换。这些信息可以推断出关于受害者用户账户的先前提问的答案。请查看以下提供的示例:
- Is the user currently logged in?
中文:用户当前是否已登录? - Is the user ID 1337?
中文:用户ID是否为1337? - Is the user an administrator?
中文:用户是否是管理员? - Does the user have a person with a particular email address in their contact list?
中文:用户联系人列表中是否有特定电子邮件地址的人?
On the basis of such questions, the attacker might try to deduce the answers, depending on the application's context. In most cases, the answers will be in binary form (yes or no). The impact of this vulnerability depends strongly on the application's risk profile. Despite this, XS Leaks may pose a real threat to user privacy and anonymity.
基于此类问题,攻击者可能会根据应用程序的上下文尝试推断答案。在大多数情况下,答案将以二进制形式(是或否)呈现。该漏洞的影响强烈取决于应用程序的风险配置文件。尽管如此,XS Leaks可能对用户隐私和匿名性构成实际威胁。
Attack vector / 攻击向量¶


- The entire attack takes place on the victim's browser side - just like an XSS attack
中文:整个攻击发生在受害者的浏览器端 - 就像一次 XSS 攻击一样 - In some cases, the victim must remain on the attacker's site longer for the attack to succeed.
中文:在某些情况下,受害者必须停留在攻击者的网站上更长的时间,攻击才能成功。
Same Origin Policy (SOP) / 同源策略 (SOP)¶
Before describing attacks, it's good to understand one of the most critical security mechanisms in browsers - The Same-origin Policy. A few key aspects:
在描述攻击之前,了解浏览器中最关键的安全机制之一——同源策略——是很有帮助的。几个关键方面:
- Two URLs are considered as same-origin if their protocol, port, and host are the same
中文:如果两个 URL 的 协议、端口 和 主机 相同,则被认为是 同源的** - Any origin can send a request to another source, but due to the Same-origin Policy, they will not be able to read the response directly
中文:任何来源都可以向另一个源发送请求,但由于同源策略,它们将无法直接读取响应 - Same Origin Policy may be relaxed by Cross Origin Resource Sharing (CORS).
中文:同源策略可能通过 跨源资源共享 (CORS) 来放宽。
| Origin A / 源 A | Origin B / 源 B | Same origin? / 同源吗? |
|---|---|---|
https://example.com |
http://sub.example.com |
No, different hosts 不,不同的主机 |
https://example.com |
https://example.com:443 |
Yes! Implicit port in Origin A 是的!源A中的隐式端口 |
Although the SOP principle protects us from accessing information in cross-origin communication, XS-Leaks attacks based on residual data can infer some information.
尽管 SOP 原则保护我们免受跨源通信中访问信息的影响,但基于残留数据的 XS-Leaks 攻击可以推断出一些信息。
SameSite Cookies¶
The SameSite attribute of a cookie tells the browser whether it should include the cookie in the request from the other site. The SameSite attribute takes the following values:
Cookie 的 SameSite 属性指示浏览器是否应将 Cookie 包含在其他站点的请求中。SameSite 属性可以取以下值:
None- the cookie will be attached to a request from another site, but it must be sent over a secure HTTPS channel
中文:None- Cookie 将附加到来自另一个站点的请求,但必须通过安全的 HTTPS 通道发送Lax- the cookie will be appended to the request from another page if the request method is GET and the request is made to top-level navigation (i.e. the navigation changes the address in the browser top bar)
中文:Lax- 如果请求方法是 GET 并且请求是针对顶级导航(即导航更改浏览器顶部栏中的地址),则 Cookie 将附加到来自另一个页面的请求Strict- the cookie will never be sent from another site
中文:Strict- Cookie 从不会从另一个站点发送
It is worth mentioning here the attitude of Chromium based browsers in which cookies without SameSite attribute set by default are treated as Lax.
在此值得提及基于 Chromium 的浏览器的态度,其中默认未设置 SameSite 属性的 Cookie 被视为 Lax。
SameSite cookies are a strong defense-in-depth mechanism against some classes of XS Leaks and CSRF attacks, which can significantly reduce the attack surface, but may not completely cut them (see, e.g., window-based XS Leak attacks like frame counting and navigation).
SameSite Cookie 是一种强大的 多层次防御机制,针对 某些类别的 XS 泄露和 CSRF 攻击,可以显著减少攻击面,但可能无法完全切断(例如,参见 基于窗口的 XS 泄露 攻击,如 框架计数 和 导航)
How do we know that two sites are SameSite? / 我们如何知道两个站点是 SameSite?¶


For SameSite cookies, a site consists of the scheme and the registrable domain (eTLD+1). The port is not part of the site. For example:
对于 SameSite 糖果,一个 网站 由方案和可注册域名(eTLD+1)组成。端口号不是网站的一部分。例如:
| Full URL / 完整URL | Site (scheme + eTLD+1) / 站点(方案 + eTLD+1) |
|---|---|
https://example.com:443/data?query=test |
https://example.com |
Why are we talking about eTLD+1 and not just TLD+1? It's because of domains like .github.io or .eu.org. Such parts are not atomic enough to be compared well. For this reason, a list of "effective" TLDs (eTLDs) was created and can be found here.
为什么我们谈论 eTLD+1 而不是 TLD+1?这是因为像 .github.io 或 .eu.org 这样的域名部分不足以进行良好的比较。因此,创建了一个“有效”的顶级域名(eTLDs)列表,可以在这里找到。
Sites with the same scheme and eTLD+1 are considered same-site. For example:
具有相同方案和 eTLD+1 的网站被认为是同一网站的。例如:
| Origin A / 源 A | Origin B / 源 B | SameSite? |
|---|---|---|
https://example.com |
http://example.com |
No, different schemes |
https://evil.net |
https://example.com |
No, different eTLD+1 |
https://sub.example.com |
https://data.example.com |
Yes, subdomains don't matter |
For more information about SameSite, see the excellent article Understanding "same-site".
有关 SameSite 的更多信息,请参阅优秀的文章理解“同一网站”。
Attacks using the element ID attribute / 使用元素 ID 属性的攻击¶
Elements in the DOM can have an ID attribute that is unique within the document. For example:
DOM 中的元素可以具有在文档中唯一的 ID 属性。例如:
<button id="pro">Pro account</button>
The browser will automatically focus on an element with a given ID if we append a hash to the URL, e.g. https://example.com#pro. What's more, the JavaScript focus event gets fired. The attacker may try to embed the application in the iframe with specific source on its own controlled page:
浏览器会自动聚焦于带有指定ID的元素,如果我们向URL中添加一个哈希值,例如 https://example.com#pro。此外,JavaScript的聚焦事件会被触发。攻击者可能会尝试在其自己控制的页面上使用特定源将应用程序嵌入iframe中:

then add listener in main document for blur event (the opposite of focus). When the victim visits the attackers site, the blur event gets fired. The attacker will be able to conclude that the victim has a pro account.
然后在主文档中添加对失焦事件(聚焦事件的相反)的监听器。当受害者访问攻击者的网站时,失焦事件会被触发。攻击者将能够得出受害者拥有专业账户的结论。
Defense / 防御措施¶
Framing protection / 框架保护¶
If you don't need other origins to embed your application in a frame, you can consider using one of two mechanisms:
如果您不需要其他源在iframe中嵌入您的应用程序,您可以考虑使用以下两种机制之一:
- Content Security Policy
frame-ancestorsdirective. Read more about syntax.
中文:内容安全策略frame-ancestors指令。 了解更多关于语法的详情 - X-Frame-Options - mainly if you want to support old browsers.
中文:X-Frame-Options - 主要用于支持旧浏览器。
Setting up framing protection efficiently blocks the ability to embed your application in a frame on the attacker-controlled origin and protects from other attacks like Clickjacking.
有效地设置框架保护可以阻止在攻击者控制的源上嵌入您的应用程序,并保护免受其他攻击,如点击劫持。
Fetch metadata (Sec-Fetch-Dest) / 获取元数据(Sec-Fetch-Dest)¶
Sec-Fetch-Dest header provides us with a piece of information about what is the end goal of the request. This header is included automatically by the browser and is one of the headers within the Fetch Metadata standard.
Sec-Fetch-Dest头部为我们提供了关于请求最终目标的信息。此头部由浏览器自动包含,是Fetch Metadata标准中的头部之一。
With Sec-Fetch-Dest you can build effective own resource isolation policies, for example:
使用Sec-Fetch-Dest可以构建有效的资源隔离策略,例如:
app.get('/', (req, res) => {
if (req.get('Sec-Fetch-Dest') === 'iframe') {
return res.sendStatus(403);
}
res.send({
message: 'Hello!'
});
});

If you want to use headers from the Fetch Metadata standard, make sure that your users' browsers support this standard (you can check it here). Also, think about using the appropriate fallback in code if the Sec-Fetch-* header is not included in the request.
如果您想使用Fetch Metadata标准中的头部,请确保用户的浏览器支持此标准(您可以在这里进行检查)。同时,如果请求中未包含Sec-Fetch-*头部,请考虑在代码中使用适当的回退方案。
Attacks based on error events / 基于错误事件的攻击¶
Embedding from resources from other origins is generally allowed. For example, you can embed an image from another origin or even script on your page. What is not permitted is reading cross-origin resource due the SOP policy.
从其他源嵌入资源通常是允许的。例如,您可以从另一个源嵌入图片或甚至是在您页面上嵌入脚本。不允许的是由于SOP策略而读取跨源资源。
When the browser sends a request for a resource, the server processes the request and decides on the response e.g. (200 OK or 404 NOT FOUND). The browser receives the HTTP response and based on that, the appropriate JavaScript event is fired (onload or onerror).
当浏览器发送一个资源请求时,服务器处理请求并决定响应,例如(200 OK或404 NOT FOUND)。浏览器接收HTTP响应,并根据此响应触发适当的JavaScript事件(onload或onerror)。
In this way, we can try to load resources and, based on the response status, infer whether they exist or not in the context of the logged-in victim. Let's look at the following situation:
通过这种方式,我们可以尝试加载资源,并根据响应状态推断它们是否存在于已登录受害者的上下文中。让我们看看以下情况:
GET /api/user/1234- 200 OK - currently logged-in user is 1234 because we successfully loaded resource (onload event fired)
中文:GET /api/user/1234- 200 OK - 当前登录用户是 1234,因为我们成功加载了资源 (onload 事件已触发)GET /api/user/1235- 401 Unauthorized - 1235 is not the ID of the currently logged in user (onerror event will be triggered)
中文:GET /api/user/1235- 401 未授权 - 1235 不是当前登录用户的 ID (onerror 事件将被触发)
Given the above example, an attacker can use JavaScript on his controlled origin to guess the victim's ID by enumerating over all the values in a simple loop.
根据上述示例,攻击者可以使用其控制的源上的JavaScript通过遍历简单循环中的所有值来猜测受害者的ID。
function checkId(id) {
const script = document.createElement('script');
script.src = `https://example.com/api/users/${id}`;
script.onload = () => {
console.log(`Logged user id: ${id}`);
};
document.body.appendChild(script);
}
// Generate array [0, 1, ..., 40]
const ids = Array(41)
.fill()
.map((_, i) => i + 0);
for (const id of ids) {
checkId(id);
}
Note that the attacker here does not care about reading the response body even though it would not be able to due to solid isolation mechanisms in browsers such as Cross-Origin Resource Blocking. All it needs is the success information it receives when the onload event fires.
请注意,这里的攻击者即使由于浏览器中如跨源资源阻塞等严格的隔离机制而无法读取响应体,他也不关心这一点。他只需要在onload事件触发时接收到的成功信息。
Defense / 防御措施¶
SubResource protection / 子资源保护¶
In some cases, mechanism of special unique tokens may be implemented to protect our sensitive endpoints.
在某些情况下,可能会实现特殊唯一令牌的机制来保护我们的敏感端点。
/api/users/1234?token=be930b8cfb5011eb9a030242ac130003
- Token should be long and unique
中文:令牌应该长且唯一 - The back-end must correctly validate the token passed in the request
中文:后端必须正确验证请求中传递的令牌
Although it is pretty effective, the solution generates a significant overhead in proper implementation.
尽管这种方法相当有效,但在实施过程中会产生显著的开销。
Fetch metadata (Sec-Fetch-Site) / 获取元数据(Sec-Fetch-Site)¶
This header specifies where the request was sent from, and it takes the following values:
此头指定了请求是从哪里发送的,它接受以下值:
cross-site
中文:跨站same-origin
中文:同源same-site
中文:同站none- user directly reached the page
中文:无 - 用户直接访问页面
Like Sec-Fetch-Dest, this header is automatically appended by the browser to each request and is part of the Fetch Metadata standard. Example usage:
类似于Sec-Fetch-Dest,这个头部由浏览器自动附加到每个请求中,是Fetch元数据标准的一部分。示例用法:
app.get('/api/users/:id', authorization, (req, res) => {
if (req.get('Sec-Fetch-Site') === 'cross-site') {
return res.sendStatus(403);
}
// ... more code
return res.send({ id: 1234, name: 'John', role: 'admin' });
});
Cross-Origin-Resource-Policy (CORP) / 跨源资源策略(CORP)¶
If the server returns this header with the appropriate value, the browser will not load resources from our site or origin (even static images) in another application. Possible values:
如果服务器返回此头部并带有适当的值,浏览器将不会从我们的站点或源加载资源到另一个应用程序中(即使是静态图像)。可能的值:
same-site
中文:同站same-origin
中文:同源cross-origin
中文:跨源
Read more about CORP here.
了解更多关于CORP 这里。
Attacks on postMessage communication / 针对postMessage通信的攻击¶
Sometimes in controlled situations we would like, despite SOP, to exchange information between different origins. We can use the postMessage mechanism. See below example:
有时在受控情况下,尽管违反了操作规程(SOP),我们仍希望在不同源之间交换信息。我们可以使用postMessage机制。以下是一个示例:
// Origin: http://example.com
const site = new URLSearchParams(window.location.search).get('site'); // https://evil.com
const popup = window.open(site);
popup.postMessage('secret message!', '*');
// Origin: https://evil.com
window.addEventListener('message', e => {
alert(e.data) // secret message! - leak
});
Defense / 防御措施¶
Specify strict targetOrigin / 指定严格的目标源¶
To avoid situations like the one above, where an attacker manages to get the reference for a window to receive a message, always specify the exact targetOrigin in postMessage. Passing to the targetOrigin wildcard * causes any origin to receive the message.
为了避免上述情况,即攻击者设法获取接收消息的窗口的引用,始终在postMessage中指定确切的targetOrigin。将targetOrigin传递给通配符*会导致任何源接收消息。
// Origin: http://example.com
const site = new URLSearchParams(window.location.search).get('site'); // https://evil.com
const popup = window.open(site);
popup.postMessage('secret message!', 'https://sub.example.com');
// Origin: https://evil.com
window.addEventListener('message', e => {
alert(e.data) // no data!
});
Frame counting attacks / 框架计数攻击¶
Information about the number of loaded frames in a window can be a source of leakage. Take for example an application that loads search results into a frame, if the results are empty then the frame does not appear.
关于窗口中加载的框架数量的信息可能是一个泄露源。例如,一个将搜索结果加载到框架中的应用程序,如果结果为空,则框架不会出现。

An attacker can get information about the number of loaded frames in a window by counting the number of frames in a window.frames object.
攻击者可以通过计算window.frames对象中的框架数量来获取窗口中加载的框架数量的信息。
So finally, an attacker can obtain the email list and, in a simple loop, open subsequent windows and count the number of frames. If the number of frames in the opened window is equal to 1, the email is in the client's database of the application used by the victim.
因此,攻击者可以获取电子邮件列表,并通过简单的循环打开后续窗口并计算框架数量。如果打开的窗口中的框架数量等于1,则电子邮件在受害者使用的应用程序的客户数据库中。
Defense / 防御措施¶
Cross-Origin-Opener-Policy (COOP) / 跨源开启策略(COOP)¶
Setting this header will prevent cross-origin documents from opening in the same browsing context group. This solution ensures that document A opening another document will not have access to the window object. Possible values:
设置此头部将防止跨源文档在相同的浏览上下文组中打开。此解决方案确保文档A打开另一个文档时无法访问window对象。可能的值:
unsafe-nonesame-origin-allow-popupssame-origin
中文:同源
In case the server returns for example same-origin COOP header, the attack fails:
如果服务器返回例如same-origin COOP头部,攻击将失败:
const win = window.open('https://example.com/admin/customers?search=john%40example.com');
console.log(win.frames.length) // Cannot read property 'length' of null
Attacks using browser cache / 利用浏览器缓存的攻击¶
Browser cache helps to significantly reduce the time it takes for a page to load when revisited. However, it can also pose a risk of information leakage. If an attacker is able to detect whether a resource was loaded from the cache after the load time, he will be able to draw some conclusions based on it.
浏览器缓存有助于显著减少页面在重新访问时的加载时间。然而,它也可能带来信息泄露的风险。如果攻击者能够检测到资源在加载后是否来自缓存,他将能够根据这些信息得出一些结论。
The principle is simple, a resource loaded from cache memory will load incomparably faster than from the server.
原理很简单,从缓存内存加载的资源将比从服务器加载快得多。

An attacker can embed a resource on their site that is only accessible to a user with the admin role. Then, using JavaScript, read the load time of a particular resource and, based on this information, deduce whether the resource is in cache or not.
攻击者可以在他们的网站上嵌入一个只有管理员角色用户才能访问的资源。然后,使用JavaScript读取特定资源的加载时间,并根据这些信息推断该资源是否在缓存中。
The example selects the first matching resource timing entry already recorded in the timeline. Set THRESHOLD for the measurement context; a short duration alone does not prove a cache hit.
示例选择时间轴中已记录的第一个匹配资源计时条目。为测量上下文设置THRESHOLD;仅短持续时间并不能证明缓存命中。
// Illustrative timing threshold for this measurement context
// const THRESHOLD = ...
const adminImagePerfEntry = window.performance
.getEntriesByType('resource')
.find((entry) => entry.name.endsWith('admin.svg'));
if (adminImagePerfEntry && adminImagePerfEntry.duration < THRESHOLD) {
console.log('Possible cache hit (timing heuristic)');
}
Defense / 防御措施¶
Unpredictable tokens for images / 图像的不规则令牌¶
This technique is accurate when the user wants the resources to still be cached, while an attacker will not be able to find out about it.
当用户希望资源仍然被缓存,而攻击者无法发现这一点时,此技术是准确的。
/avatars/admin.svg?token=be930b8cfb5011eb9a030242ac130003
- Tokens should be unique in context of each user
中文:令牌应在每个用户的上下文中是唯一的 - If an attacker cannot guess this token, it will not be able to detect whether the resource was loaded from cache
中文:如果攻击者无法猜出这个令牌,它将无法检测资源是否是从缓存中加载的
Using the Cache-Control header / 使用Cache-Control头¶
You can disable the cache mechanism if you accept the degraded performance related to the necessity of reloading resources from the server every time a user visits the site. To disable caching for resources you want to protect, set the response header Cache-Control: no-store.
如果您接受每次用户访问网站时从服务器重新加载资源所必需的性能下降,则可以禁用缓存机制。要禁用您想要保护的资源的缓存,请设置响应头Cache-Control: no-store。
Quick recommendations / 快速建议¶
- If your application uses cookies, make sure to set the appropriate SameSite attribute.
中文:如果你的应用程序使用cookie,请确保设置适当的SameSite属性。 - Think about whether you really want to allow your application to be embedded in frames. If not, consider using the mechanisms described in the framing protection section.
中文:考虑你是否真的希望你的应用程序被嵌入到框架中。如果不是,请考虑使用框架保护部分中描述的机制。 - To strengthen the isolation of your application between other origins, use Cross Origin Resource Policy and Cross Origin Opener Policy headers with appropriate values.
中文:为了加强你的应用程序与其他源之间的隔离,使用带有适当值的跨源资源共享策略和跨源打开策略头部。 - Use the headers available within Fetch Metadata to build your own resource isolation policy.
中文:使用Fetch元数据中可用的头部来构建你自己的资源隔离策略。
References / 参考¶
- MDN: Cross-Site Leaks) (MDN:跨站泄露)
- W3C: Fetch Metadata Request Headers) (W3C:Fetch元数据请求头)