VSCode漏洞致GitHub Token一键被盗
速览
研究人员发现VSCode存在安全漏洞,可导致GitHub访问令牌被一键窃取。该漏洞利用VSCode的特定功能,使攻击者能够绕过常规安全机制获取敏感凭证。此发现提醒开发者需及时更新软件并加强令牌管理。
AI 深度解读
1-Click GitHub Token Stealing via a VSCode Bug
背景
你是否知道 GitHub 有一个名为 github.dev 的酷炫功能?
对于你有权访问的任何代码仓库,只需将 URL 中的 github.com 更改为 github.dev,或者点击那个小菜单项,你就会进入一个完全在浏览器中运行的轻量级 VSCode 实例。这得益于 VSCode 基于 Electron 架构的特性,使其能够以 Web 形式运行。
这个浏览器端的 VSCode 实例功能相当强大:你可以查看仓库中的所有文件(即使是私有仓库),发送拉取请求(Pull Requests),甚至提交代码。
这种功能是通过 github.com 向 github.dev POST 一个 OAuth 令牌来实现的,该令牌允许 github.dev 代表你与 GitHub 进行交互。需要注意的是,该令牌的范围并不局限于你当前交互的特定仓库,这意味着它拥有对你所有可访问仓库的完全访问权限。
鉴于该令牌的存在,以及这个 Web 应用几乎运行了 VSCode 整个百万行 TypeScript 代码库的核心部分,它成为了任何试图挖掘 VSCode 漏洞的人的理想目标。本文将探讨此类漏洞,并展示攻击者如何利用它来窃取你的 GitHub 令牌。
核心内容
VSCode Webview 的安全模型
作为桌面端的 Electron 应用,如果在 VSCode 内部执行任意 JavaScript,等同于实现了完整的远程代码执行(RCE)。因此,VSCode 实施了一些沙箱机制,其中我们关注的重点是 Webviews。
Webviews 使用与主 VSCode 窗口不同源(origin)的 <iframe>,以确保在其中执行的任何 JavaScript 完全隔离。Webviews 用于 Markdown 预览或编辑 Jupyter 笔记本等功能:
- Jupyter 单元格的输出渲染到源为
vscode-webview://...的<iframe>中。 - 主 Electron 窗口的源为
vscode-file://...。
这意味着,即使 Jupyter 笔记本使用内置功能显示 HTML 或使用 JavaScript 进行交互式小部件操作,VSCode 的核心应用程序也能得到保护。无法在该 iframe 内使用 Electron 与 Node.js API 的集成,也无法从该帧调用 VSCode 的 API。
跨源通信与消息传递
虽然静态内容渲染是安全的,但缺乏交互性。为了实现如 Markdown 预览高亮当前编辑器行或实时预览等功能,主编辑器窗口需要与 vscode-webview://... 帧中的 DOM 进行交互。
然而,跨源策略(Cross-Origin Policy)阻止了主窗口直接访问 iframe 内的 DOM。正如你不想让一个嵌入 google.com 的 iframe 能够读取你的 Cookie 或改变网站行为一样,VSCode 也遵循这一原则。
唯一的解决方案是允许不同源的两个网页通过 Window.postMessage() API 进行合作。该方法允许在不同窗口之间发送 JavaScript 对象。例如,在显示 Markdown 渲染行与编辑器行对应关系时,主编辑器窗口会发送如下消息:
{
type: "onDidChangeTextEditorSelection",
line: 31
}
Webview 内部的相应代码会监听此消息并添加高亮:
window.addEventListener('message', async event => {
const data = event.data as ToWebviewMessage.Type;
switch (data.type) {
// ...
case 'onDidChangeTextEditorSelection':
marker.onDidChangeTextEditorSelection(data.line, documentVersion);
return;
}
});
漏洞原理:键盘事件劫持
Webview 的安全边界大致如下:虽然 JS 被隔离,但 UI 交互(如点击链接、拖放、Ctrl+F)需要在 Webview 内部正常工作。为此,VSCode 通过消息传递机制实现了一些基本功能。
这里存在一个关键的安全隐患:键盘事件。
如果攻击者在一个页面中嵌入了另一个跨源页面(例如在 hackerman.com 中嵌入 google.com/login),他们不应能够向 iframe 附加键盘监听器,否则他们可以窃听你的所有按键(包括密码)。
然而,为了提供良好的用户体验(避免用户在 Webview 内时快捷键失效),VSCode 的默认 Webview 消息处理器包含一个名为 did-keydown 的事件。当加载 Webview 时,以下代码会在 Webview 内部运行以注册处理程序:
contentWindow.addEventListener('keydown', handleInnerKeydown);
/**
* @param {KeyboardEvent} e
*/
const handleInnerKeydown = (e) => {
// ...
hostMessaging.postMessage('did-keydown', {
key: e.key,
keyCode: e.keyCode,
code: e.code,
shiftKey: e.shiftKey,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
repeat: e.repeat
});
};
这使得 Webview 能够向上冒泡 keydown 事件,以便主 VSCode 窗口将其视为用户键盘事件无缝处理。
漏洞核心:没有任何机制阻止在不受信任的 Webview 中运行的脚本假装是用户,并代表用户按下大量按键。
攻击者可以模拟按键序列来唤起命令面板并执行危险命令,例如安装受攻击者控制的扩展程序。理论上需要的序列是:
Ctrl+Shift+P(唤起命令面板)- 输入
developer: install extension from location Enter- 输入
<攻击者控制的扩展路径> Enter
利用难点与突破
虽然可以发送对应的 keydown 事件,但浏览器不会将其视为用户手动输入。因此,VSCode 会弹出命令面板,但如果 VSCode 没有手动拦截每个字符的 keydown 事件,事件将无法在面板中输入文本。
事实上,命令面板小部件使用的是 HTML <input> 标签,因此它不监听 keydown 事件来处理字符输入,而是使用上下箭头键滚动列表,并使用 Enter 键选择命令。
最终利用路径:
VSCode 拥有大量默认键盘快捷键,其中许多直接监听 keydown 事件。经过测试,最容易利用的是 “通知:接受通知主要操作” (Notifications: Accept Notification Primary Action)。
默认的快捷键绑定是 Ctrl+Shift+A,它将点击 VSCode 中最后弹出的通知的主要按钮。
攻击者可以这样做:
- 在仓库的
.vscode/extensions.json文件中推荐恶意扩展:{ "recommendations": [ "HackerMan.my-malicious-extension" ] } - 当用户打开该仓库时,VSCode 会弹出通知,建议安装该扩展。
- 攻击者通过 Webview 注入脚本,模拟
Ctrl+Shift+A按键。 - 该按键操作会自动接受通知并安装恶意扩展。
- 恶意扩展一旦安装,即可在 VSCode 环境中获得完全代码执行权限,进而窃取 OAuth 令牌。
关键要点
- 令牌权限过大:
github.dev使用的 OAuth 令牌拥有对用户所有可访问仓库的完全读写权限,而不仅限于当前查看的仓库。 - Webview 隔离机制:VSCode 使用
vscode-webview://...源的 iframe 隔离 Web 内容,防止直接 DOM 访问和 Node.js API 调用。 - 通信依赖消息传递:主窗口与 Webview 通过
Window.postMessage()通信,包括键盘事件(did-keydown)。 - 键盘事件冒泡漏洞:Webview 将
keydown事件冒泡到主窗口以支持快捷键,但这允许恶意脚本模拟按键序列。 - 利用默认快捷键:攻击者利用
Ctrl+Shift+A(接受通知)这一默认快捷键,结合.vscode/extensions.json中的扩展推荐,诱导用户安装恶意扩展。 - 远程代码执行风险:一旦恶意扩展被安装,攻击者即可在 VSCode 环境中执行任意代码,从而窃取敏感的 GitHub 令牌。
意义与影响
- 供应链攻击向量:此漏洞揭示了通过代码仓库元数据(如
