突破配置完善的VSCode扩展(获利篇)- Trail of Bits博客
Vasco Franco
2023年2月23日
漏洞披露
在这个由两部分组成的系列文章的第一部分中,我们成功逃逸了现实世界中配置不当的VSCode扩展的Webview。但如果扩展配置完善,我们是否还能实现逃逸?
本文将展示我如何通过利用浏览器(即Electron创建的Chromium实例,VSCode及其Webview在此运行)与其他VSCode逻辑之间在URL解析上的微小差异,以及对浏览器路径规范化的过度依赖,成功绕过Webview的localResourceRoots限制。此绕过允许在Webview内执行JavaScript的攻击者读取系统任意位置的文件,包括localResourceRoots之外的文件。微软将此漏洞分配为CVE-2022-41042,并奖励我们7500美元(约合每分钟发现漏洞2500美元)。
发现问题
在利用上一篇文章中详述的漏洞时,我思考VSCode本身是否存在漏洞,可能允许我们绕过限制Webview功能的任何安全特性。特别是,我好奇如果Webview的localResourceRoots选项设置了更严格的规则,我们是否还能利用在SARIF Viewer扩展中发现的漏洞(第一部分中的漏洞1)。
从上一篇文章的SARIF查看器利用中,我们了解到如果满足以下前提条件,始终可以通过DNS预取泄露文件:
- 可以在Webview中执行JavaScript。这使您能够向DOM添加链接标签。
- CSP的connect-src指令包含.vscode-resource.vscode-cdn.net源。这使您能够获取本地文件。
…但仅限于localResourceRoots文件夹内的文件!此选项限制Webview可以读取文件的文件夹,在SARIF查看器中,它被配置为限制…好吧,什么都没有。但如此宽松的localResourceRoots配置很少见。大多数扩展只允许访问当前工作区和扩展文件夹中的文件(localResourceRoots选项的默认值)。
回想一下,Webview通过获取https://file+.vscode-resource.vscode-cdn.net“伪”域来读取文件,如下例所示。
从VSCode扩展Webview获取文件的示例
甚至没有查看代码如何强制执行localResourceRoots选项,我就开始尝试不同的路径遍历payload,目标是逃逸我们被限制的根目录。我尝试了几种payload,例如:
|
|
正如预期,这没有奏效。浏览器甚至在请求到达VSCode之前就规范化了请求路径,如下图所示。
对/etc/passwd文件的不成功获取
我开始尝试浏览器不会规范化但某些VSCode逻辑可能认为是有效路径的不同变体。大约三分钟后,令我惊讶的是,我发现使用%2f..而不是/..允许我们逃逸根文件夹(!!!)。
使用/字符URL编码为%2f时成功获取/etc/passwd文件
我们逃逸了!我们现在可以从文件系统的任何位置获取文件。但为什么这能奏效?VSCode似乎解码了%2f,但我并不真正理解底层发生了什么。我最初的假设是读取文件的函数(例如fs.readFile函数)正在解码%2f,而路径规范化函数没有。正如我们将看到的,这不是一个糟糕的猜测,但并非真正的原因。
根本原因分析
让我们从头开始,看看VSCode如何处理vscode-resource.vscode-cdn.net请求——记住,这不是一个真实域名。
一切始于在Webview上运行的服务工作者。此服务工作者拦截Webview对vscode-resource.vscode-cdn.net域的每个请求,并将其转换为对主VSCode线程的postMessage(’load-resource’)。
来自Webview服务工作者的代码,拦截以vscode-resource.vscode-cdn.net开头的获取请求,并将其转换为对主VSCode线程的postMessage(来源)
VSCode将通过构建URL对象并调用loadResource来处理postMessage(’load-resource’)调用,如下所示。
处理load-resource postMessage的VSCode代码。红色高亮显示解码获取路径的代码——我们的利用工作的第一个原因。(来源)
注意URL路径使用decodeURIComponent解码。这就是我们的%2f被解码的原因!但这单独仍不能解释路径遍历为何奏效。在检查路径是否属于某个根之前规范化路径会阻止我们的利用。让我们继续。
loadResource函数简单地使用roots: localResourceRoots调用loadLocalResource。
调用loadLocalResource并带有localResourceRoots选项的loadResource函数(来源)
然后,loadLocalResource函数调用getResourceToLoad,它将遍历localResourceRoots中的每个根,并检查请求的路径是否在这些根之一中。如果所有检查通过,loadLocalResource读取并返回文件内容,如下所示。
检查路径是否在预期根文件夹内并在成功时返回文件内容的代码。红色高亮显示没有任何先前规范化的.startsWith检查——我们的利用工作的第二个原因。(来源)
总之,两个错误允许我们的利用:
- VSCode扩展在路径上调用decodeURIComponent(path),将%2f解码为/。这允许我们绕过浏览器的规范化并在路径中引入../序列。
- containsResource函数使用startsWith函数检查请求的文件是否在预期的localResourceRoots文件夹内,而没有首先规范化路径(即移除../序列)。这允许我们使用诸如[valid-root-path]/../../../的payload遍历到根之外。
仅通过手动审计代码很难发现此漏洞。抽象层和所有消息传递掩盖了我们的数据流经的位置,以及使利用工作的一些关键细节。这就是为什么通过执行代码并在运行时观察其行为(动态分析)来评估和测试软件是审计复杂系统的重要组成部分。通过静态分析发现此漏洞需要定义源、汇、清理器以及能够理解在postMessage调用中传递的数据的过程间引擎。完成所有这些工作后,您可能仍会得到许多误报和漏报;我们在Trail of Bits广泛使用静态分析工具,但它们不适合此项工作。
防止路径遍历的建议
在上一篇博客的第三个漏洞中,我们检查了一个路径遍历漏洞,该漏洞由使用有缺陷的手工编码逻辑解析URL查询字符串引起,这允许我们绕过浏览器完成的路径规范化。这些漏洞非常相似;在这两种情况下,URL解析差异和对浏览器进行路径规范化的依赖导致了具有关键后果的路径遍历漏洞。
因此,在处理URL时,我们建议遵循以下原则:
- 使用适当的对象(例如JavaScript的URL类)从路径解析URL,而不是手工编码逻辑。
- 除非有充分理由,否则在规范化后不要转换任何URL组件。正如我们所见,即使使用decodeURIComponent(path)调用解码路径也足以完全绕过localResourceRoots特性,因为代码的其他部分假设浏览器已经规范化了路径。如果您想了解更多关于URL解析差异及其如何导致关键漏洞的信息,我推荐阅读Orange Tsai的《SSRF的新时代》和《利用URL解析混淆》。
- 在检查文件是否在预期根内之前,始终规范化文件路径。将这两个操作一起执行,最好在同一个封装函数中,确保没有未来或现有代码会以任何使规范化操作无效的方式转换路径。
时间线
- 2022年9月7日:向微软报告漏洞
- 2022年9月16日:微软确认报告行为并提到案例正在审查以可能获得奖励
- 2022年9月20日:微软将报告标记为奖励范围外,因为“VS代码扩展不符合奖励资格”
- 2022年9月21日:我回复指出漏洞在VSCode与扩展交互的方式中,而不是在VSCode扩展中
- 2022年9月24日:微软承认错误并奖励漏洞7500美元
- 2022年10月11日:微软在PR #163327中修复漏洞并分配CVE-2022-41042
如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News
页面内容 发现问题 根本原因分析 防止路径遍历的建议 时间线 近期文章 使用Deptective调查您的依赖项 系好安全带,Buttercup,AIxCC的评分回合正在进行中! 使您的智能合约超越私钥风险成熟化 Go解析器中意想不到的安全陷阱 我们从审查首批DKLs中学到的内容 Silence Laboratories的23个库 © 2025 Trail of Bits. 使用Hugo和Mainroad主题生成。