突破VSCode扩展安全配置:路径遍历漏洞的发现与利用

本文详细分析了VSCode Webview中一个关键安全漏洞CVE-2022-41042,通过利用浏览器与VSCode的URL解析差异,成功绕过localResourceRoots限制实现任意文件读取,并获得微软7500美元漏洞赏金。

突破配置完善的VSCode扩展(获利篇)- Trail of Bits博客

发现漏洞

在利用上一篇文章中详细描述的漏洞时,我思考VSCode本身是否存在允许我们绕过限制Webview功能的安全特性的错误。特别地,我好奇如果Webview的localResourceRoots选项有更严格的规则,我们是否仍然能够利用在SARIF Viewer扩展中发现的漏洞(第一部分中的漏洞1)。

从上一篇SARIF viewer漏洞利用中,我们了解到如果满足以下前提条件,你总是可以通过DNS预取泄露文件:

  • 你可以在Webview中执行JavaScript。这使你能够向DOM添加链接标签。
  • CSP的connect-src指令包含.vscode-resource.vscode-cdn.net源。这使你能够获取本地文件。

…但仅限于localResourceRoots文件夹内的文件!此选项限制了Webview可以读取文件的文件夹,在SARIF viewer中,它被配置为限制…实际上没有限制。但如此宽松的localResourceRoots很少见。大多数扩展只允许访问当前工作区和扩展文件夹中的文件(localResourceRoots选项的默认值)。

回想一下,Webviews通过获取https://file+.vscode-resource.vscode-cdn.net"伪"域来读取文件,如下例所示。

从VSCode扩展Webview获取文件的示例

甚至没有查看代码如何强制执行localResourceRoots选项,我就开始尝试不同的路径遍历payload,目标是逃离我们被囚禁的根目录。我尝试了几个payload,例如:

1
2
3
/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。

loadResource函数使用localResourceRoots选项调用loadLocalResource(来源)

然后,loadLocalResource函数调用getResourceToLoad,它将迭代localResourceRoots中的每个根,并检查请求的路径是否在这些根之一中。如果所有检查都通过,loadLocalResource读取并返回文件内容,如下所示。

检查路径是否在预期根文件夹内并在成功时返回文件内容的代码。红色高亮显示了没有任何事先规范化的.startsWith检查——我们的漏洞利用工作的第二个原因。(来源)

总之,两个错误允许我们的漏洞利用:

  1. VSCode扩展在路径上调用decodeURIComponent(path),将%2f解码为/。这使我们能够绕过浏览器的规范化并在路径中引入../序列。
  2. 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。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计