0%

0x00 背景

前两天苹果通过全新的“后台安全改进”功能推送了一个webkit漏洞的补丁CVE-2026-20643,漏洞描述:

A cross-origin issue in the Navigation API was addressed with improved input validation.

WebKit Bugzilla: 306050

CVE-2026-20643: Thomas Espach

既然是webkit,那岂不是可以去GitHub commit记录里捞补丁了,于是就去webkit的commit记录里找(瞎猜)了一下。

0x01 关于Navigation API

简单来说,Navigation API 是 Web 平台为了解决 单页应用(SPA)路由管理 痛点而推出的“现代版”浏览器历史记录与导航管理接口。

在它出现之前,我们一直在忍受复杂的 window.historypopstate 事件。到了 2026 年,Navigation API 已经正式成为主流浏览器的“基准特性”(Baseline),彻底改变了前端路由的写法。

其中,chrome最早支持,大概在2022年,而safari和Firefox分别是2026年初和2025年底正式提供了支持,版本号如下:

image.png

来源:https://developer.mozilla.org/en-US/docs/Web/API/Navigation#browser_compatibility

0x02 漏洞分析

既然是Navigation API,同时又提到了cross-origin issue,那搜索的目标大概就能围绕navigation/canIntercept之类的关键词了,大概搜了一下GitHub,就发现了这个commit记录:

NavigationEvent#canIntercept is true when navigating to a different port

意思是canIntercept的实现没有严格按照whatwg文档的描述:

If targetURL and documentURL differ in their schemeusernamepasswordhost, or port components, then return false.

相同域名不同端口的地址canIntercept也返回了true,从而导致了像从localhost:3000localhost:3001 这样不同端口的跳转event也可以被Navigation API拦截,符合公告里的cross-origin描述。

修改的代码是Source/WebCore/page/Navigation.cpp ,增加了对上面几个参数的验证:

1
2
3
4
5
6
7
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#can-have-its-url-rewritten
if (documentURL.protocol() != targetURL.protocol()
|| documentURL.user() != targetURL.user()
|| documentURL.password() != targetURL.password()
|| documentURL.host() != targetURL.host()
|| documentURL.port() != targetURL.port())
return false;

同时commit记录里也给了测试用例navigate-event-canintercept-cross-port.html‎,直接把html和js依赖下载放到本地server,用safari打开页面测试就行了。

试了下已打了补丁的iPhone打开的结果:

IMG_4518.png

没打补丁的:

IMG_0004.png

做了一个去除测试框架js的精简版验证poc,这样看起来更直观一点:

https://www.flvin.com/CVE-2026-20643/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<meta charset="utf-8">
<title>Navigation API: canIntercept should be false for cross-port navigations</title>
<body>

<!-- log area -->
<div id="log"></div>

<script>

let log = function(...args) {
let originalLog = console.log;
originalLog.apply(console, args);
const logdiv = document.getElementById('log');
logdiv.innerHTML += `<p>${args.join(' ')}</p>`;
};

const currentPort = location.port || (location.protocol === 'https:' ? '443' : '80');
const otherPort = currentPort === '8800' ? '8000' : '8800';
const targetURL = `${location.protocol}//${location.hostname}:${otherPort}/`;


navigation.addEventListener("navigate", (event) => {
// Cross-port navigation should have canIntercept === false
log(`event.canIntercept: ${event.canIntercept}`);

const url = new URL(event.destination.url);
log(`Navigating to: ${url.href} (port: ${url.port})`);

if (event.canIntercept !== false) {
log(`canIntercept should be false when navigating from port ${currentPort} to port ${otherPort}`);

}

// Prevent the actual navigation
event.preventDefault();

});


// Trigger the cross-port navigation
const anchor = document.createElement('a');
anchor.href = targetURL;
document.body.appendChild(anchor);
anchor.click();


</script>
</body>
</html>

如果页面和console里显示event.canIntercept: true说明存在漏洞。

0x03 后记

打补丁的方式参照苹果的文档:https://support.apple.com/zh-cn/102657

如果没有自动安装可以手动选择安装。

今天突然想起去GitHub上搜一下这个CVE,然后发现苹果刚发补丁的那天就有大佬靠逆向扒出补丁了orz:https://github.com/zeroxjf/WebKit-NavigationAPI-SOP-Bypass

image.png

看来猜对了。

今天在thehackernews上看到一篇讲黑客利用Google Tag Manager外挂恶意js脚本的文章(https://thehackernews.com/2025/02/hackers-exploit-google-tag-manager-to.html),然后顺带搜了一下,发现利用GTM加载js的攻击早就有了(https://www.recordedfuture.com/research/threat-actors-continue-to-abuse-google-tag-manager-for-payment-card-e-skimming),这篇里面甚至列了很多样本。
那么GTM怎样来使用呢,下面就来简单尝试一下利用GTM来执行自定义的js脚本。

GTM创建demo

  1. 首先来到GTM控制台,在account创建账号,然后创建容器。
  2. 进入容器之后添加一个新的tag,选择custom html
    Pasted image 20250211162440.png
    Pasted image 20250211162609.png
  3. 添加自定义的js代码,使用script标签Pasted image 20250211162744.png
  4. 添加好之后就能在tags列中看见了Pasted image 20250211162902.png
  5. 点右上角的submit发布,再点开旁边的tag id就能看到给页面添加GTM的代码了Pasted image 20250211163113.png
  6. 把tag id拼接到https://www.googletagmanager.com/gtm.js?id= 后面在浏览器中打开,就能看到完整的js,而我们添加进去的自定义js就在vtp_html属性中Pasted image 20250211163348.png
  7. 把第5步中的代码添加到网站的html页面中,就能看到自定义js代码被执行了Pasted image 20250211163553.png

总结

其实就一点,相较于从别的域名加载js,使用类似GTM这种可信域名加载恶意js更容易绕过安全检测(可能一看是Google的域名就跳过了),同时也提醒我们不能简单的按照域名白名单放行安全检查。
最后,用Gemini的deep research扒了一下相关资料,链接放在下面
https://gemini.google.com/share/a0b63a35f98a

在现代 web 开发中,Blob(Binary Large Object) 是一个重要的概念,广泛应用于处理二进制数据,如图像、视频等。本文将分享我如何利用 ChatGPT,从初步了解 Blob 的原理,到最终生成一个用于下载视频的 Chrome 插件的完整过程。希望能为正在进行类似项目的开发者提供一些参考和启发。

目录

  1. 引言
  2. 初探 Blob API
  3. 利用 ChatGPT 深入理解 Blob
  4. 从 Blob 到 Chrome 插件开发
  5. 实现 Chrome 插件
  6. 总结
  7. 参考资料
  8. License

引言

故事要从一个朋友问我的问题说起,买的网课要到期了来不及看完,手机录屏录不了有没有什么办法可以把视频都录下来。我说,有网页版吗,打开QQ录屏就行了,试了一下确实可以,It works! 不过,对于专业人士来说,不够优雅,于是我打算让ChatGPT老师帮我解决这个问题,从询问原理开始,最终达成了用ChatGPT写出了一个自动下载视频的Chrome插件。

初探 Blob API

Blob(Binary Large Object)是用于存储二进制数据的对象,常用于处理文件数据、图像、视频等。在浏览器中,Blob 常与 URL.createObjectURL 方法配合使用,将二进制数据转换为 URL,从而在页面上展示或下载。

基本使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个 Blob 对象
const blob = new Blob(["Hello, world!"], { type: "text/plain" });

// 生成一个 Blob URL
const url = URL.createObjectURL(blob);

// 在页面中使用 Blob URL
const link = document.createElement('a');
link.href = url;
link.download = 'hello.txt';
link.textContent = 'Download Hello.txt';
document.body.appendChild(link);

通过上述方法,可以在不上传服务器的情况下,生成可供下载的文件链接。

利用 ChatGPT 深入理解 Blob

在决定开发 Chrome 插件之前,我需要深入理解 Blob 的工作原理以及如何在网页中拦截和操作 Blob URL。这时,我利用了 ChatGPT 的强大语言理解和代码生成能力。

询问 Blob 原理

首先,我向 ChatGPT 提出了关于 Blob 原理的问题:

问题:什么是 Blob?它在浏览器中的作用是什么?

ChatGPT 的回答:Blob(Binary Large Object)是一种用于存储二进制数据的对象,常用于处理文件、图像、视频等。在浏览器中,Blob 主要用于以下几个方面:

  • 文件上传和下载:通过 Blob 可以生成可供下载的文件链接。
  • 图像和视频处理:使用 Blob 存储和操作媒体数据。
  • 数据传输:在客户端和服务器之间传输二进制数据时,Blob 起到中间媒介的作用。

ChatGPT 的详细解释帮助我全面掌握了 Blob 的基本概念和应用场景,为后续的插件开发奠定了坚实的理论基础。

生成代码示例

接下来,我需要在实际项目中操作 Blob。因此,我向 ChatGPT 请求生成相关的代码示例:

问题:如何在网页中拦截 Blob URL 并下载对应的视频文件?

ChatGPT 的回答

要拦截 Blob URL 并下载对应的视频文件,可以通过重写 URL.createObjectURL 方法,并监听视频元素的相关事件。以下是一个基本的实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function() {
// 保存原始的 createObjectURL 方法
const originalCreateObjectURL = URL.createObjectURL;

// 重写 createObjectURL 方法
URL.createObjectURL = function(blob) {
const blobUrl = originalCreateObjectURL.call(this, blob);
console.log('Blob URL created:', blobUrl);

// 检查 Blob 的类型是否为视频
if (blob.type.startsWith('video/')) {
// 创建一个下载链接
const link = document.createElement('a');
link.href = blobUrl;
link.download = `video_${Date.now()}.mp4`;
link.click();
console.log('Video downloaded:', link.download);
}

return blobUrl;
};
})();

这个代码片段通过重写 URL.createObjectURL 方法,拦截生成的 Blob URL,并在识别到视频类型的 Blob 时,自动创建并点击下载链接,将视频文件保存到本地。

通过 ChatGPT 生成的代码示例,我对如何操作和拦截 Blob URL 有了更直观的理解,也为后续的插件开发提供了实用的参考。

从 Blob 到 Chrome 插件开发

了解了 Blob 的原理和操作方法后,我决定将其应用到 Chrome 插件的开发中。目标是创建一个插件,能够在用户访问包含视频的网页时,自动拦截并下载正在播放的视频。

具体步骤包括:

  1. 需求分析:明确插件的功能和工作流程。
  2. 编写内容脚本:负责在加载网页时注入拦截逻辑。
  3. 修改 Blob 相关方法:实现拦截和下载功能。
  4. 管理视频数据:处理被拦截的视频数据并保存。
  5. 配置manifest.json:定义插件的基本信息和权限。
  6. 测试插件:确保插件在不同网页和视频环境中正常工作。

实现 Chrome 插件

需求分析

插件的主要功能是:

  • 拦截视频加载:识别并拦截通过 Blob URL 加载的视频资源。
  • 自动下载视频:将拦截到的视频数据保存到用户的本地设备。
  • 用户友好:提供友好的提示和下载管理功能。

编写内容脚本

内容脚本是插件的核心部分,负责在网页加载时注入拦截逻辑。以下是我的 content.js 实现:

1
2
3
4
5
6
7

var script = document.createElement('script');
script.src = chrome.runtime.getURL('script.js');
console.log(script.src);
(document.head || document.documentElement).appendChild(script);

// 以下是注入的脚本,将在页面上下文中执行

这个脚本的作用是动态加载 script.js,将拦截逻辑注入到网页的上下文中。(这里需要解释一下,为什么需要注入脚本,而不是直接在网页中执行,因为chrome插件的执行环境是独立的,直接执行可以操作DOM,但hook JavaScript方法并不会影响网页中的JavaScript执行环境)

修改 Blob 相关方法

script.js 中,我重写了 URL.createObjectURLURL.revokeObjectURL 方法,以实现对 Blob URL 的拦截和管理。在执行URL.createObjectURL的时候自动创建下载链接,并触发下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function hookCreateObjectURL() {
// 保存原始的 createObjectURL 方法
const originalCreateObjectURL = URL.createObjectURL;

// 自定义的 hook 方法
URL.createObjectURL = function (blob) {
// 添加自定义逻辑
console.log(`[Extension Hooked] URL.createObjectURL called with: ${blob}, type: ${blob.type}`);

console.log(`[Extension Hooked] blob size: ${blob.size}`);

// 调用原始方法
const blobUrl = originalCreateObjectURL.call(URL, blob);

// 可选:执行其他操作
console.log(`[Extension Hooked] Blob ${blobUrl} has been created.`);

// 只保存视频类型
if(blob.type === 'video/mp4'){
const link = document.createElement('a');
link.href = blobUrl;
link.download = Date.now() + blobUrl + 'saved.mp4';
link.click();
console.log(`[Extension Hooked] Blob ${blobUrl} has been saved.`);
}

return blobUrl;
};
console.log('createObjectURL hooked');
})();

获取视频缓存数据

为了更好地管理视频数据,我在 script.js 中添加了对 SourceBuffer 方法的重写,以便捕获视频分段数据,并在视频加载完成后,合并并下载完整的视频文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const sourceBufferMap = new Map();

(function hookAppendBuffer() {
// 保存原始的 revokeObjectURL 方法
const originalAppendBuffer = SourceBuffer.prototype.appendBuffer;

// 自定义的 hook 方法
SourceBuffer.prototype.appendBuffer = function (data) {
// 添加自定义逻辑
console.log(`[Extension Hooked] SourceBuffer.appendBuffer called with: ${data}`);
if(sourceBufferMap.has(this)){
var mediaSegment = sourceBufferMap.get(this);
mediaSegment.push(data);
}else{
sourceBufferMap.set(this, []);
var mediaSegment = sourceBufferMap.get(this);
mediaSegment.push(data);
}

// 调用原始方法
originalAppendBuffer.apply(this, [data]);

// 可选:执行其他操作
console.log(`[Extension Hooked] source ${data} has been appended.`);
};
console.log('SourceBuffer.appendBuffer hooked');

})();

在视频缓存结束时,我们需要将收集到的视频片段合并并下载。以下是对 endOfStream 方法的 hook 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(function hookEndOfStream() {
// 保存原始的 revokeObjectURL 方法
const originalEndOfStream = MediaSource.prototype.endOfStream;

// 自定义的 hook 方法
MediaSource.prototype.endOfStream = function () {
// 添加自定义逻辑
console.log(`[Extension Hooked] MediaSource.endOfStream called`);

//哪个MediaSource的endOfStream触发就下载哪个里面的sourceBuffers
for (const sourceBuffer of this.sourceBuffers) {
console.log('SourceBuffer found:', sourceBuffer);
console.log('Buffered ranges:', sourceBuffer.buffered);

const completeVideo = new Blob(sourceBufferMap.get(sourceBuffer), { type: 'video/mp4' });
const url = URL.createObjectURL(completeVideo);
URL.revokeObjectURL(url);
}

// 调用原始方法
originalEndOfStream.apply(this);

};
console.log('MediaSource.endOfStream hooked');

})();

通过这种方式,我们能够捕获视频的每个数据分段,最终合并成完整的视频文件进行下载。

配置 manifest.json

manifest.json 是 Chrome 插件的配置文件,定义了插件的基本信息、权限和脚本等。以下是我的 manifest.json 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"manifest_version": 3,
"name": "Preload Hook Example",
"version": "1.0",
"description": "Hook fetch before page load",
"permissions": ["tabs", "storage", "activeTab", "scripting"],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"web_accessible_resources" : [{
"resources": [ "script.js" ],
"matches": [ "http://*/*", "https://*/*" ]
}]
}

在这个配置中:

  • permissions:声明了插件所需的权限,如访问标签页(tabs)、存储(storage)、活动标签页(activeTab)和脚本注入(scripting)。
  • content_scripts:定义了在哪些网页上注入哪些脚本,以及脚本的执行时机(document_start 表示在文档开始加载时执行)。
  • web_accessible_resources:指定了插件可以在网页上下文中访问的资源,这里是 script.js

测试插件

完成上述步骤后,我开始在本地环境中测试插件的功能。具体步骤如下:

  1. 加载未打包的插件

    • 打开 Chrome 浏览器,进入 chrome://extensions/ 页面。
    • 打开右上角的“开发者模式”。
    • 点击“加载已解压的扩展程序”,选择包含 manifest.json 的插件目录。
  2. 访问包含视频的网页

    • 测试了小红书、B站、YouTube并成功下载视频,其中小红书视频是一整个mp4文件(包含视频流和音频流),B站和YouTube都是一段视频流+一段音频流,需要合并后才能播放。
  3. 调试和优化

    • 根据测试结果,调整拦截逻辑和下载机制,确保插件在不同网页和视频类型下都能正常工作。

总结

通过本文的分享,从初步了解 Blob 原理,到借助 ChatGPT 实现 Chrome 插件的开发,整个过程展示了 AI 工具在开发中的巨大潜力。ChatGPT 不仅帮助我快速掌握了相关技术,还提供了实用的代码示例,加速了项目进展。

最后,为什么不是用获取视频流地址的方式下载,而是提取Blob里的缓存呢,存在几个原因:

  1. 获取视频流地址需要对每个不同的网站进行分析,而提取Blob里的缓存则可以统一处理。
  2. 有些网站可能会对视频流中的二进制流进行加密,导致直接下载到的视频无法播放,而提取缓存则是解密后的视频。
  3. 访问视频流地址下载会产生额外的http请求,而提取缓存则是在本地进行操作,对于服务端而言没有任何感知,减少因为下载视频被block的风险。

当然,提取缓存的方式也有缺点,就是只能等待浏览器缓存完成,缓存了多少,下载就有多少,无法做到按需下载。

未来,随着 AI 技术的不断进步,相信开发者们将在更多领域中受益。希望本文能为大家提供一些有价值的参考,激发更多创新的灵感。

参考资料

License

本文遵循 CC BY-SA 4.0。转载请注明出处。


以上就是使用 ChatGPT 从初步了解 Blob 原理到最终开发出一个功能完整的 Chrome 插件的全过程。希望本文能够帮助到正在进行类似项目的开发者,共同推动 Web 技术的发展!

最后,本文也是使用cursor中的ChatGPT自动生成。