突破配置完善的VSCode扩展(获利)- Trail of Bits博客
在本系列的第一部分中,我们逃逸了现实世界中配置错误的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,例如:
- /etc/passwd
- /../../../../../etc/passwd
- [valid_root]/../../../../../etc/passwd
正如我所料,这没有奏效。浏览器甚至在请求到达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。
调用带有localResourceRoots选项的loadLocalResource的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解析器中意想不到的安全隐患 我们审查首批DKLs23库的收获 来自Silence Laboratories的23个库 © 2025 Trail of Bits。 使用Hugo和Mainroad主题生成。