‧
阅读时长17分钟
漏洞复盘:2023年7月
Metabase 团队
‧ 阅读时长17分钟
分享本文
在过去的两个星期里,Metabase 经历了一次项目有史以来最严重的漏洞。我们知道这个漏洞给你们,我们的社区,带来了极大的困扰,特别抱歉不得不连续两个周五发布升级通知。
现在情况基本稳定,我们想向大家说明发生了什么,以及我们为什么这么做。我们知道,在没有解释的情况下要求“立即升级”是模糊的,需要我们的社区对我们的判断有很大的信任。我们希望通过透明地说明发生了什么(包括好的和坏的方面),社区能够继续信任我们。
故事的核心是一系列相当棘手的漏洞,为了更好地理解时间线上的事件,了解实际涉及的漏洞是有益的。
温馨提示——这是一篇相当长且技术性很强的文章,面向的是管理和保护 Metabase 的用户。
发生了什么
漏洞
这一系列漏洞的核心是我们支持的一个数据库——H2。H2 是一个基于 JVM 的嵌入式数据库。我们将 H2 用作 Metabase 的“自带”数据库,用于存储用户账户、仪表板定义、设置等。当我们在评估发送示例数据的方法时,H2 在这方面也成为了首选。在将 H2 用于发送我们的示例数据库时,我们还决定允许用户连接他们可能拥有的任何 H2 数据库。虽然它从未被广泛使用,但它“免费附带”,所以我们基本上保留了 H2 作为支持的选项。
由于 H2 在实际使用中,大部分是嵌入在其他应用程序中使用的,它有很多功能并非为多客户端设计。更具体地说,有一些方法可以让 H2 在进程内运行解释器,还有连接字符串参数可以让你运行 SQL(或其他任何东西),并且通常缺乏针对恶意客户端的加固措施。我们之前发现并修复了一种这样做的方法(通过连接字符串上的 `INIT` 参数),但最近的漏洞(或者更准确地说是一系列三个不同的漏洞)源于此。
存在三个不同的问题
- 你可以使用 Unicode(例如,用 ï 代替 I)来绕过我们对 `INIT` 关键字的检查,因为 H2 会在后台将其转换(感谢 Reginaldo Silva)。
- 你可以通过在详细信息负载中隐藏 H2 连接字符串,来欺骗 Metabase 将数据库连接视为 PostgreSQL 连接,但让 JDBC `DriverManager` 加载 H2 驱动程序(感谢 Chaitin Security Response Institute 和 bluE0)。
- H2 中的 `TRACE_LEVEL_SYSTEM_OUT` 选项容易受到 SQL 注入攻击(感谢 AssetNote 和 Maxwell Garrett)。
到目前为止,这些问题构成了一系列非常令人烦恼的漏洞,允许任何添加或验证新数据库连接的人闯入运行 Metabase 的宿主操作系统。通常情况下,宿主操作系统访问仅限于 Metabase 管理员,所以虽然这无疑是一个问题,但它还不是一个“stop-the-world”级别的事件。
但是,还有另外两个关键的拼图碎片。
首先,人们经常会弄错连接字符串,在确认用户输入的连接字符串之前进行测试连接是很常见的。我们提供了一个 API 调用来促进在保存数据库连接之前进行此检查。在添加数据库的常规管理员面板流程中,这一点受到管理员权限 API 检查的保护。
最后,也是最令人尴尬的一块拼图:我们有一个设置过程,它通过一个 API 调用创建用户账户并连接数据库。我们将这两个操作合并为一个步骤,并暴露了一个未经身份验证的 API 端点 `/api/setup/validate`,该端点会尝试连接到数据库连接字符串。该端点只有在与 `setup_token` 一起使用时才能访问。这个 `setup_token` 永远不应该公开,并且 Metabase 通常会在首次使用后立即删除该令牌。然而,去年年初,我们进行了一些更改,允许通过环境变量注入该令牌,并无意中将该令牌泄露到了 `/api/session/properties` 中暴露的设置中(此外,未能正确清除首次使用后的令牌)。
这些因素,加上让 H2 执行宿主操作系统命令的能力,导致了未经身份验证的远程代码执行,并毁掉了 Metabase 社区连续两个周五的时光。
将所有这些放在一起,一个人可以
- 调用 `/api/session/properties` 来获取设置令牌。
- 使用设置令牌调用 `/api/setup/validate`。
- 利用缺失的检查,让 H2 在宿主操作系统上执行命令。
- 打开反向 shell,创建管理员账户等。
简而言之,这是一个极其严重的攻击链。
初步报告和修复(7月13日至21日)
7月13日,我们收到了一位外部研究人员关于应用程序中安全漏洞的报告。该报告详细说明了上述(非常容易)的路径,可以在无需任何身份验证的情况下在 Metabase 服务器上运行自定义代码(我们将此初步漏洞称为“AssetNote”漏洞,因为 Assetnote 团队报告了它)。
到7月14日,我们为该漏洞编写、测试并构建了修复程序。我们将其推送到了我们为 Metabase Cloud 客户托管的 2000 多个服务器上。
然而,此时我们面临一个棘手的问题:接下来该怎么做?我们有数百家自托管客户,我们有合同和道德责任来保护他们。我们还有 50,000 多个组织在自己的服务器上运行 Metabase。一旦知道漏洞的存在,利用该漏洞就很容易,而且虽然利用该漏洞访问底层数据仓库需要一些特定的技能,但利用该漏洞进行 DDoS、垃圾邮件和加密货币挖掘几乎不需要任何技能或努力。我们也没有任何方法可以强制任何人升级,甚至无法向运行 Metabase 服务器的人发送电子邮件。
标准的做法是发布补丁,告知全世界有关补丁的信息,让那些没有付费给我们管理服务器的人自生自灭。如果这个漏洞是一个普通的、不允许世界上任何地方的任何人访问任何 Metabase 实例中的用户数据的漏洞,我们就会这样做。
我们真的想做得更好。经过一番思考,我们提出了一个三阶段计划。
- 向付费客户提供打好补丁的二进制文件。对于运行自定义分支的客户,我们将提供实际源代码补丁,但需签署 NDA。
- 一周后,公开发布修复程序,但不包含源代码。通知我们的社区这是一个未经身份验证的 RCE(远程代码执行),并且必须立即升级。
- 又过一周,将修复程序合并到我们的主公共存储库中,并发布 CVE,归功于所有帮助我们发现和修复此问题的人。
在这个计划中,我们做了一个非常明确的权衡。我们知道 Clojure JAR 可以被反编译,并且可以检查反编译的 JVM 源代码(Clojure 特别经常在 jar 中包含源代码)。我们还从打好补丁的二进制文件中剥离了源代码。在整个发布过程中,我们知道不可能在不让复杂的研究人员或攻击者了解该错误是什么以及如何利用它的情况下提供修复。但我们希望我们能够为我们的安装基础争取尽可能多的升级天数,然后再让漏洞进入广泛传播。
抓紧时间,然后等待(7月18日至21日)
7月18日,我们已经准备好了打好补丁的二进制文件,并向我们的自托管客户宣布了。
此初步补丁
- 如果实例已初始化(即完成了设置流程),则完全阻止使用易受攻击的端点。
- 检测到传递到数据库连接字符串的某些字符串,这些字符串使得第一次攻击成为可能,影响了 `/api/setup/validate` 端点(公开,无需身份验证)和 `/api/database`(私有,需要身份验证)。如果发现,这些将被返回一个错误,而不是传递到 H2。
我们与这些客户共享了所有受影响版本的已打补丁的二进制文件,但没有共享源代码。对于运行自定义分支的客户,我们要求他们直接与我们联系,并根据 NDA 为他们提供了补丁。大约有二十名客户运行自定义分支并与我们取得了联系。
到目前为止,一切顺利。
在接下来的三天里,发生了一些改变我们计划的事件。
等待并观察(7月21日至27日)
我们收到了来自客户的两份看似独立的报告,称他们已经知道了该漏洞。当时,这非常令人惊讶,因为该漏洞存在一年多才被发现,并且利用该漏洞需要相当特定地利用我们产品中的一些漏洞。我们进一步深入研究,得知两位客户都得到了最初发现该漏洞的研究团队的通知(两位客户都为 Metabase 安全问题提供赏金)。
这,加上自定义分支的数量,让我们非常担心让我们的 OSS 社区在没有提前警告或可用修复程序的情况下继续使用。因此,我们决定加速发布时间表,但仍然暂时不披露实际的漏洞利用。
7月21日,我们发布了推文,更新了我们的博客,在 LinkedIn 发布了消息,并向我们整个收到了数万人订阅的更新邮件列表发送了邮件。
我们开始监控社交媒体,看是否有相关信息出现,但几天都没有发现任何相关内容,包括在 Hacker News 上。在几天里,有一些兴趣,但没有关于任何人反编译并重现漏洞的报道。
7月26日,我们收到了另一份安全报告( henceforth “Qing” vulnerability),详细介绍了如何通过使用连接详细信息中的一个单独键(而不是在同一个连接字符串中)来绕过对 `/api/database` 端点(仅限于已登录管理员)的控制,以及一种 AssetNote 漏洞的变体,该变体使用与外部数据库的连接来运行命令。
虽然令人不安,但这些发现需要已登录管理员,并且只要部署了之前的已打补丁的二进制文件,就不可能发生未经身份验证的 RCE。
7月27日,一位工程师(henceforth “Reginaldo”)成功逆向工程了我们的补丁,并发现了一个针对公开的 `/api/setup/validate` 端点的新攻击。这次攻击使用了一个变音符号(添加到字母上的符号)来绕过我们的一些控制,但该漏洞仅影响未初始化的实例(处于设置模式的实例),以及另一个也影响 `/api/database`(私有)端点的漏洞,该漏洞将 H2 服务器打开为一个可以运行命令的外部数据库。
这稍微提高了风险,因为现在可能存在针对已打补丁的实例的未经身份验证的 RCE——尽管仅限于实例首次设置的几分钟内。
赶往公开版本(7月28日至31日)
当天晚些时候(美国时区是晚上),第四组安全研究人员在未事先联系我们的情况下发布了一篇博客文章。该文章包含“AssetNote”漏洞的详细信息,导致 AssetNote 发布了他们的发现(因为猫已经跑出来了)。
7月28日醒来看到,这篇帖子包含另一个“隐藏”的攻击,作者“留给读者自行发现”。我们与他们联系,他们告知了我们一个先前未知的方法,可以通过 JDBC `DriverManager` 访问 H2。
我们在构建 46.6.3 版本(修复“Qing”和“Reginaldo”漏洞的补丁)的同一时刻,得知了这种新攻击。
我们发布了该补丁,同时,为了更稳妥,我们决定发布另一个次要版本(46.6.4),移除了 H2 作为支持的分析数据库。由于很明显,更广泛的安全社区已经意识到了该漏洞的根本原因,我们还发布了一篇博客文章,并通过电子邮件通知我们的客户和 OSS 用户升级,或在升级之前阻止所有相关端点。
在此期间,我们为 Metabase Cloud 客户的服务器缓解了问题。一旦我们得知每个漏洞,我们就对所有相关 API 端点设置了网络级阻止,关闭了我们的商店以禁止新用户注册,并开始了审查我们所有日志的乐趣。到周六晚上(7月29日),我们单独审计了所有调用易受攻击端点的服务器,并且没有发现任何被篡改的证据。我们仍在密切关注任何可疑活动,并正在进行大量内部讨论,以从中吸取教训。
7月31日,我们又向社区发布了一条推文和 LinkedIn 帖子。
总而言之,我们设法在发布通用补丁版本和漏洞进入大众视野之间争取到了一周左右的混淆努力时间。不理想,但希望这给了更多人升级的时间。
当前情况
TL;DR:如果您是自托管用户,并且上次升级是在 2023 年 7 月 28 日之前,请立即升级。
如果您是 Metabase Cloud 客户,您不受影响。
如果您是自托管用户,并且正在运行最新的二进制文件(目前为 46.6.4),您是安全的。
如果您运行的是 Metabase 版本 43-45,并且尚未升级到最新次要版本 43.7.3、44.7.3、45.4.3 或更高版本,则您易受攻击。
如果您运行的是 42 或更低版本,您不受此漏洞的影响。但您可能面临许多其他安全问题和错误,因此我们建议您尽快升级。
我们从这次事件中学到了什么?
好吧,我们了解到,如果您发布了一个没有信息的已打补丁的二进制文件,最多需要五天时间才能反编译字节码,并识别和重现底层漏洞。
我们了解到,客户端提供的连接字符串应受到更严格的审查,并且 JDBC 驱动程序不可信。我们最初对 `setup_token` 被用于创建其他管理员用户特别警惕,但了解到我们也应该对任何打开数据库连接的操作同样警惕。
我们对我们的日志记录、事件响应和入侵检测流程进行了实际运行,并确定了需要改进的领域。
我们仍在进行事后分析,并在未来几天内无疑会学到更多。
附录
我如何知道我是否/曾经受到影响
如果您是 Metabase Cloud 客户,据我们所能找到的信息,您未受到任何影响。我们一收到报告就立即修补并阻止了所有漏洞。我们所有的 Kubernetes Pod 实例都已回收并从已知安全的镜像重新创建。我们审查了所有客户的日志,回溯了几周,没有发现任何可疑的利用迹象。我们将继续监控我们的基础设施,以确保您的安全。
如果您是自托管 Metabase
这些漏洞自 2022 年 5 月以来一直存在,因此,如果您保存了 Metabase 实例的日志,或者在 Metabase 实例前面有一个负载均衡器/反向代理,您应该检查以下模式
如果在日志中看到在您的实例初始设置之后的任何时间收到了对 `/api/setup/validate` 的 `POST` API 调用,并且返回的状态码为 `2xx` 或 `400`,而您运行的版本不是我们在 2023 年 7 月 18 日发布的版本,那么您的实例可能已被泄露(即:您的实例可能已被攻击者控制)。
如何知道攻击者深入到何种程度,以及信息是否被泄露
有两种类型的攻击
- 如果您看到返回状态码 `2xx` 的调用,那么您将无法看到攻击者在您的实例上运行了什么代码,因此您应该考虑最坏的情况:攻击者进入了服务器,获取了应用程序数据库的凭证,并能够连接到它,他们还泄露了您连接的数据仓库的凭证。
- 如果您看到返回状态码 `400` 的调用,那么 Metabase 日志将打印攻击者在您的实例上运行的代码。例如:

从这一点开始,攻击者将能够获取 Metabase 应用程序数据库的连接字符串,其中又包含数据仓库的连接字符串。
如何知道攻击者是否打开了反向 shell
如果您在运行 Metabase 的服务器或容器中运行 `top`,并看到一个带有参数(如 `echo`、编码参数等)的 bash 进程正在运行,则该实例已打开反向 shell(例如,截图中的 `PID 3763`)。

如果您在运行 Metabase 的服务器或容器中运行 `netstat -antp`,并看到 Metabase 不使用的端口的活动连接,并且连接处于 `ESTABLISHED` 状态(例如,截图中的来自远程地址 `172.23.0.2:9001` 的连接)。

如果 shell 已退出一段时间,您还可能会看到处于 `FIN_WAIT2` 状态的连接,如本例所示

我该怎么办?
如果您怀疑自己可能受到了影响,我们建议您
- 将您的实例升级到最新的二进制文件,使用一个新的服务器实例。
- 轮换您的数据仓库凭证。
- 限制从未知主机访问您的数据仓库的 IP。
- 检查您的 Metabase 实例上是否没有不熟悉的用户名。
- 轮换在 Metabase 中使用的 Slack 和 SMTP 凭证令牌。
此漏洞正在被积极利用
我们知道一些恶意软件开发者/僵尸网络已将漏洞代码包含在他们的攻击中。我们还知道至少有一个攻击 Metabase 服务器以挖掘加密货币并将其用作 DDoS 攻击节点的实例。
我们不知道在提到的端点中存在其他漏洞。