SonarCloud在OpenRefine中发现了一个严重的Zip Slip 漏洞。
如果运行易受攻击版本的用户被诱骗导入恶意项目,攻击者就可以在用户的计算机上执行任意代码。
SonarCloud不仅发现了该漏洞,还就如何缓解此类漏洞和防止常见陷阱提供了宝贵的指导。
该漏洞已在版本 3.7.4中修复。
OpenRefine是一个基于Java的开源数据清理和转换工具。这包括加载不同类型的数据、清理、转换和扩展。所有这些都可以通过浏览器访问 OpenRefine 的 Web 界面来完成。它拥有近 10k star 和约 1.8k fork,是较受欢迎的GitHub 项目之一。
在我们不断努力帮助保护开源项目并改进我们的清洁代码解决方案的过程中,我们定期通过 SonarCloud 扫描开源项目并评估结果。事实上,每个人都可以做到——SonarCloud是一款针对开源项目的免费代码分析产品,无论其规模或语言如何。
SonarCloud 报告的调查结果之一是 OpenRefine 中的 Zip Slip 漏洞,这让我们感到好奇。Zip Slip 漏洞是由于提取档案时路径验证不充分而导致的,这可能允许攻击者覆盖现有文件或将文件提取到非预期位置。
在本文中,我们概述了此漏洞的影响,并解释了如何使用 SonarCloud 检测此漏洞和其他代码漏洞。此外,我们还解释了攻击者如何利用该漏洞,并描述了开发人员在尝试修复该漏洞时可能陷入的典型陷阱。
OpenRefine 3.7.3 及以下版本在项目导入功能中容易出现Zip Slip 漏洞( CVE-2023-37476)。尽管 OpenRefine 设计为仅在用户计算机上本地运行,但攻击者可以诱骗用户导入恶意项目文件。一旦导入该文件,攻击者就可以在用户的计算机上执行任意代码:
在本节中,我们将深入探讨该漏洞的技术细节。
SonarCloud 是我们基于云的代码分析服务。它使用最先进的静态代码分析技术来查找代码中的质量问题、错误和安全漏洞。通过最近添加的更深入的 SAST技术,甚至可以发现因使用第三方依赖项而引入的隐藏安全漏洞。
在我们对公共开源项目的定期扫描期间,引擎在 OpenRefine 中报告了以下问题(请自行在 SonarCloud 上查看):
通过突出显示的代码流可以清楚地看到,该untar
方法迭代存档中的所有文件,并使用该tarEntry.getName()
方法创建一个新File
对象,然后将其传递到FileOutputStream
以提取该文件。这引入了 Zip Slip 漏洞,允许攻击者destDir
通过使用名为../../../../tmp/pwned
.
存在漏洞的untar
方法是从该FileProjectManager.importProject
方法调用的,该方法处理现有Refine项目文件的导入:
OpenRefine/main/src/com/google/refine/io/FileProjectManager.java
public class FileProjectManager extends ProjectManager { // ... public void importProject(...) { // .. untar(destDir, inputStream);
可以通过直接上传存档或提供存档的 URL 来导入项目。该功能在 Web 界面上如下所示:
相应的端点称为/command/core/import-project
。尽管 OpenRefine 的此端点和所有其他端点不需要身份验证,但 OpenRefine 应该在用户计算机上本地运行。此外,所采用的 CSRF 保护可防止在另一个网站的上下文中执行的恶意 JavaScript 代码执行未经授权的操作。为了利用该漏洞,攻击者仍然可以诱骗用户导入恶意项目。
该漏洞为攻击者提供了强大的原语:将具有任意内容的文件写入文件系统上的任意位置。对于以root
特权运行的应用程序,有多种可能性可以将其转换为操作系统上的任意代码执行:向文件添加新用户passwd
、添加 SSH 密钥、创建 cron 作业等等。对于以低权限用户的权限运行的应用程序,机会更加有限,但仍然会发生 - 今年早些时候,我们记录了一种通过编写特定于站点的配置挂钩来实现代码执行的独特方法,该方法仅限于 Python 应用程序。
除了这些通用技术之外,应用程序本身可能还存在一些可供攻击者利用的功能。对于 OpenRefine,该应用程序实现了自动重新加载功能,该功能会定期扫描文件夹WEB-INF
以查找更改,并WebAppContext
在文件更改时重新启动:
OpenRefine/server/src/com/google/refine/Refine.java
class RefineServer extends Server { static private void scanForUpdates(...) { // ... scanList.add(new File(contextRoot, "WEB-INF/web.xml")); findFiles(".class", new File(contextRoot, "WEB-INF/classes"), scanList); findFiles(".jar", new File(contextRoot, "WEB-INF/lib"), scanList); // ... scanner.addListener(new Scanner.BulkListener() { public void filesChanged() { try { context.stop(); context.start();
WEB-INF/classes
该文件夹中的所有类都会在WebAppContext
. 这意味着攻击者可以覆盖.class
此文件夹中的现有文件,从而触发重新加载并随后执行攻击者的.class
文件,从而获得执行任意代码的能力。
为了缓解此漏洞,需要确保所有文件都提取到预期的基本文件夹下。您可能想到的一种方法是使用该getCanonicalPath
方法以字符串形式检索绝对且唯一的路径,然后利用该startsWith
方法验证目标路径是否是预期基本文件夹的一部分:
注意:这并不能完全修复漏洞! 你能发现这里的问题吗?
while ((tarEntry = tin.getNextTarEntry()) != null) { File destEntry = new File(destDir, tarEntry.getName());+ if (!destEntry.getCanonicalPath().startsWith(destDir.getCanonicalPath())) {+ throw new IllegalArgumentException("Zip archives with files escaping their root directory are not allowed.");+ }
该getCanonicalPath
方法删除了终止路径分隔符,这使得它仍然容易受到部分路径遍历的影响!
假设基文件夹 ( destDir
) 被定义为用户 john ( "/home/john/"
) 的主目录,则删除尾部斜杠,结果是"/home/john"
. 这意味着攻击者仍然可以部分路径遍历到以相同字符开头的另一个用户的主目录,例如,"/home/johnny/"
因为这通过了检查:
"/home/johnny/.ssh/id_rsa".startsWith("/home/john") == true
可以在此处找到此类部分路径遍历漏洞的现实示例, Jonathan Leitschuh 的相关 Black Hat 演讲对此进行了更详细的介绍。
我们不断跟踪新发现的此类陷阱,并将它们添加到我们的引擎中。要正确修复漏洞,您可以单击"How can I fix it?"
SonarCloud上相应问题直接附加的选项卡:
为了防止这种部分路径遍历,有两种不同的方法:
调用后重新插入基本文件夹的路径分隔符getCanonicalPath
检索Path
与 相关的对象File
并使用其startsWith
方法。这不会从字面上比较路径的字符串,而是根据路径的元素确定这一点。
对于 OpenRefine,维护者避免落入这个陷阱。他们利用以下方法正确修复了toPath
该漏洞:
while ((tarEntry = tin.getNextTarEntry()) != null) { File destEntry = new File(destDir, tarEntry.getName());+ if (!destEntry.toPath().normalize().startsWith(destDir.toPath().normalize())) {+ throw new IllegalArgumentException("Zip archives with files escaping their root directory are not allowed.");+ }
这有效地防止文件被写入目标destDir
文件夹之外。
Copyright © 2022 All Rights Reserved. 地址:上海市浦东新区崮山路538号808 苏ICP123456 XML地图