利用VSCode漏洞一键窃取GitHub Token

导语:只需点一下链接,攻击者就能拿到你GitHub账号的完整控制权——读写所有私有仓库。最近,安全研究员Ammaraskar(阿马拉斯塔卡尔)公布了这一VSCode漏洞的完整技术细节,向我们展示了攻击者如何利用WebView的消息传递机制,绕过安全边界,悄无声息地窃取你的GitHub令牌。


一、背景:github.dev的强大与风险

你知道GitHub有一个很酷的功能叫github.dev吗?

只要访问一个有权限的仓库,把URL里的github.com改成github.dev,或者点击下拉菜单里的这个选项:

下拉菜单选项用于在GitHub文件查看器中使用github.dev

你就会进入一个轻量级的VSCode界面——完全运行在浏览器里。

在github.dev(VSCode Web界面)中打开的CPython文件

这个浏览器版的VSCode功能相当强大:可以查看仓库里的所有文件(包括私有仓库),可以发送Pull Request,甚至可以直接提交代码。

实现这些功能的方法是:当你访问github.dev时,它会向GitHub发送一个POST请求,换取一个OAuth令牌,允许github.dev代表你与GitHub交互。关键问题在于,这个令牌的作用域不是仅限于你当前访问的仓库,而是对你有权限的所有其他仓库都有完全的访问权限。

这意味着,如果攻击者能拿到这个令牌,他就能读写你的所有私有仓库。

而github.dev运行着VSCode上百万行TypeScript代码库的几乎全部代码,自然成了安全研究人员挖掘漏洞的绝佳目标。

二、VSCode WebView的安全模型

VSCode是一款基于Electron的桌面应用,在桌面版中执行任意JavaScript就等同于完整的远程代码执行。因此VSCode实现了一套沙箱机制,其中最核心的就是WebView。

WebView通过与VSCode主窗口建立不同的源(origin),从而确保在其中执行的任何JavaScript都完全隔离。WebView用于Markdown预览、Jupyter Notebook编辑等功能。

Jupyter Notebook的单元格输出会渲染到一个源为vscode-webview://...中,而主Electron窗口的源则是vscode-file://...。这意味着,即使用户在Notebook中使用了HTML显示功能或JavaScript交互组件,VSCode核心程序也受到保护,无法直接访问这些内容。

很好,这让我们能渲染内容了。但静态内容太无聊,怎么实现Markdown预览时实时显示当前高亮的源代码行这样的功能呢?

Markdown预览显示相应的源代码行

这种跨域策略在保障安全的同时,也阻止了主编辑器窗口与vscode-webview://...框架中的DOM进行交互。毕竟没人希望有人利用与Google页面交互,从而窃取Cookie或改变网站行为。

要实现跨域通信,唯一的方式是让两个不同源的网页通过Window.postMessage() API进行协作。

三、漏洞:键盘事件的中继攻击

WebView的安全边界大致是这样的:

WebView安全边界

但在用户界面上,WebView就嵌入在这个窗口里。用户期望在里面点击链接、按快捷键等基本操作都能正常工作。

因此,VSCode通过消息传递机制实现了许多基本功能。说到键盘快捷键,细心的读者可能已经注意到问题了。

和大多数跨域操作一样,浏览器在两个框架之间提供了相当程度的隔离。简单说就是:如果你的页面通过iframe嵌入了Google的登录页,你肯定不希望黑客能在你的页面上给iframe添加键盘监听器,窃取用户在Google上输入的所有内容。

根据这些信息,现在试试点击VSCode WebView内部,然后按Ctrl+Shift+P调出命令面板。

哦耶,居然成功了。等一下。不对。糟了。

为了避免用户点击WebView内部时键盘快捷键失效这种糟糕体验,VSCode默认的WebView消息处理程序会注册一个did-keydown事件。当加载WebView时,会运行以下代码:

contentWindow.addEventListener('keydown', handleInnerKeydown);

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会把键盘事件冒泡上传,让VSCode主窗口把这些事件当作用户自己的键盘事件来处理。

但是——没有任何措施能阻止WebView中不受信任的脚本冒充用户,按下一堆按键。

比如,我们可以调出命令面板,然后执行危险命令,比如安装一个攻击者控制的扩展。我们只需要发送正确的事件来模拟按键操作:

  1. Ctrl+Shift+P
  2. 输入扩展安装命令
  3. Enter

但事情没那么简单。虽然我们可以发送与这个序列对应的keydown事件,但浏览器不会将其视为用户手动输入。VSCode会弹出命令面板,但我们的事件实际上并不会将文本输入到面板中——因为命令面板使用的是HTML标签,没有监听keydown事件来逐字处理输入。

不过我们可以在命令面板中上下滚动(按上下箭头键)或选择命令(按Enter),但无法输入任意文本。

幸运的是,VSCode内置了大量默认键盘快捷键,全部直接监听keydown事件。经过一番摸索,最简单的方法是利用”通知:接受通知主要操作”功能。这个快捷键Ctrl+Shift+A会点击VSCode中最后弹出的通知的主要按钮。

我们接受哪种通知?

VSCode中的推荐扩展通知

VSCode有一个功能,允许工作区通过在.vscode/extensions.json文件中添加扩展推荐,这样访问仓库时就会弹出推荐安装扩展的通知。

然后用Ctrl+Shift+A来接受安装扩展的通知,获取完整代码执行权限?就这么简单?

当然不是。从1.97版本开始,VSCode引入了新的发布者信任系统,首次从新发布者安装扩展时会弹出信任对话框:

VSCode中的发布者信任对话框

虽然我们可以用Tab键在按钮之间导航,但按Enter选择”信任发布者并安装”按钮是不可能的,因为它只监听按钮上的keydown事件,而不是整个窗口的事件。

我们可以利用VSCode的另一个功能——本地工作区扩展。只要你身处受信任的工作区(github.dev/web工作区始终受信任),就可以直接从.vscode/extensions目录安装扩展。这种方式安装的扩展会跳过发布者信任检查。

但事情没那么顺利——这样做会导致Content Security Policy(CSP)错误,因为加载扩展的工作线程期望扩展来自vscode-cdn.net。本地工作区扩展没有很好地配合Web版VSCode测试。

本地扩展程序中的内容安全策略违规

不过这只是个小小的障碍。扩展可以在package.json中向VSCode贡献额外的键盘快捷键。既然我们能可靠地触发键盘快捷键,就可以添加一个快捷键来调用我们想要的VSCode命令——比如安装扩展同时跳过发布者信任检查。

最终,我们需要的仓库要包含一个Jupyter notebook和一个本地工作区扩展。Jupyter notebook可以通过包含以下内容的Markdown单元格执行少量JavaScript:

<img src="data:foobar" onerror="javascript(); goes(); here();">

完整攻击链如下:

  1. 等待VSCode弹出询问是否安装推荐扩展的通知
  2. 发送Ctrl+Shift+A按键事件,接受通知
  3. 等待扩展安装并激活,完成自定义快捷键设置
  4. 发送Ctrl+F1按键事件,触发安装我们选定的扩展

四、PoC与自保护

详细技术分析看完了,来看看概念验证(PoC)。最大胆的读者可以直接访问这个仓库体验:

https://github.dev/ammaraskar/github-dev-token-steal-poc/blob/main/README.ipynb

这会直接启动github.dev编辑器,打开notebook,你会看到JavaScript payload正在运行的状态信息。

PoC初始页面

payload运行后,新安装的扩展会获取你的GitHub API令牌,然后查询私有仓库列表,最后在一个信息框中打印出来。

通过PoC打印的私有仓库

如果你运行了PoC,记得清除github.dev数据,或者至少卸载这个概念验证扩展,否则它会一直跟着你访问所有github.dev页面。

这个漏洞也存在于VSCode桌面版,只是利用难度更高——需要说服受害者克隆仓库并在Notebook中打开带有payload的文件。当然,如果你在WebView中还有另一个XSS漏洞能让受害者打开,那你就能在他们电脑上实现完整的远程代码执行。

如何保护自己

幸运的是,如果你从未使用过github.dev,访问网站时会弹出一个对话框让你确认。这个功能是后来才有的,因为VSCode的GitHub插件发生了变化。

初始登录对话框

这意味着如果你清除github.dev的Cookie和本地站点数据,当有人尝试对你发起这种攻击时,你可以采取行动并离开页面。

强烈建议清除github.dev的站点数据。在Chrome中,可以点击URL栏中的小图标,然后点击Cookie和网站数据 > 管理设备上的站点数据

在Chrome中管理网站数据

然后删除所有带有垃圾桶图标的域名数据:

在Chrome浏览器中删除网站数据

遗憾的是,如果你之前已经用过github.dev且没有清除浏览器的本地存储,你就完全中招了。github.dev没有任何CSRF令牌之类的保护,任何互联网上的链接都可以把你重定向到攻击页面。

五、VSCode做得好的地方与完全公开披露

VSCode不仅仅依赖,还使用了纵深防御措施,比如严格的Content Security Policy(CSP)和使用DOMPurify来清理渲染的Markdown内容,这些措施在这里发挥了重要作用。

如果有人能在扩展页面的Markdown预览中找到执行任意JavaScript的方法,你可以想象这个漏洞会造成多大的影响。但是由于使用了script-src 'none'策略,这种情况被有效地扼杀了。

扩展视图中的内容安全策略

至于为何选择完全公开披露,简单说:上次与MSRC(微软安全响应中心)打交道报告VSCode漏洞的经历非常糟糕——他们悄无声息地修复了我指出的漏洞,没有给予任何credit,还把这个漏洞标记为”没有安全影响”。所以以后发现的任何VSCode安全漏洞都会进行完全公开披露。


原文出处:1-Click GitHub Token Stealing via a VSCode Bug – Ammar’s Blog 原文链接:https://blog.ammaraskar.com/github-token-stealing/ 图片版权:本文所有图片版权归原文作者Ammaraskar所有

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容