‧
阅读时间17分钟
漏洞分析报告:2023 年 7 月
Metabase 团队
‧ 阅读时间17分钟

分享本文
在过去的两周里,Metabase 经历了项目历史上最严重的漏洞。我们知道这个漏洞对您和我们的社区造成了极大的干扰,尤其抱歉不得不在一个周五连续两次发布升级通知。
现在,尘埃基本落定,我们想阐述一下发生了什么以及我们为什么这样做。我们知道,在没有任何解释的情况下要求“立即升级”是不透明的,需要我们的社区对我们的判断有很大的信任。我们希望通过透明地公开所发生的一切(无论是好的还是坏的),社区将继续信任我们。
这个故事的核心是一系列相当恶劣的漏洞,为了最好地理解时间线中的事件,了解实际存在的漏洞是很有用的。
请注意——这是一篇相当长且技术性很强的文章,面向管理和保护 Metabase 的人员。
发生了什么
漏洞
这一系列漏洞的核心是我们支持的一个数据库——H2。H2 是一个基于 JVM 的嵌入式数据库。我们使用 H2 作为 Metabase 的“开箱即用”数据库,用于存储用户账户、仪表板定义、设置等。当我们评估发布示例数据的方式时,它也成为了首选。作为使用 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 管理员,因此虽然这确实是一个问题,但它并不是一个“停止世界”的事件。
但这个谜题还有另外两个关键部分。
首先,人们经常会搞错连接字符串,通常在确认连接字符串输入之前会进行测试连接。我们提供了一个 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 云客户托管的 2000 多个服务器。
然而,此时我们遇到了一些问题,不知道接下来该怎么做。我们有数百个自托管客户,我们有合同和道德责任来保护他们。我们还有一个拥有 50,000 多个组织在其服务器上运行 Metabase 的社区。一旦知道漏洞存在,滥用漏洞就轻而易举,虽然利用该漏洞需要一些特定的技能才能访问底层数据仓库,但用于 DDoS、垃圾邮件和加密货币挖矿几乎不需要任何技能或努力。我们也没有办法强制任何人升级,甚至无法向那些运行 Metabase 服务器的人发送电子邮件。
标准选项是发布补丁,让全世界都知道这个补丁,并让那些没有付费让我们管理其服务器的人自生自灭。如果这个漏洞是一个普通的漏洞,不会允许世界任何地方的任何人访问任何 Metabase 实例上的用户数据,我们就会这样做。
我们真的想做得更好。经过一番思考,我们最终制定了一个三阶段计划。
- 向我们的付费客户提供已打补丁的二进制文件。我们将向那些运行自定义分支的客户提供实际的源代码补丁,并签署保密协议。
- 一周后,公开发布修复程序,但不包含源代码。告知我们的社区这是一个未经身份验证的 RCE(远程代码执行),并且必须立即升级。
- 再过一周后,将修复程序合并到我们的主要公共仓库中,并发布 CVE,注明所有帮助我们发现和修复此问题的人。
在这个计划中,我们正在进行一个非常具体的权衡。我们知道 Clojure JAR 可以被反编译,反编译的 JVM 源代码可以被检查(Clojure 通常会在 JAR 中包含源代码)。我们也从我们的打补丁的二进制文件中剥离了这些源代码。在整个发布过程中,我们知道不可能在不让复杂的研发人员或攻击者知道这个 bug 是什么以及如何利用它的情况下提供修复。但我们希望我们能为我们的安装基础争取尽可能多的时间来升级,以防漏洞被广泛传播。
加快速度,然后等待(7月18-21日)
7月18日,我们准备好了已打补丁并已剥离源代码的二进制文件,并向我们的自托管客户发布了公告。
这个初步补丁
- 如果实例已初始化(即已完成设置流程),则完全阻止使用易受攻击的端点。
- 检测传递给数据库连接字符串的某些字符串(这些字符串使得第一次攻击成为可能),涉及
/api/setup/validate
端点(公共,无需身份验证)和/api/database
(私有,需要身份验证)。如果发现这些字符串,将返回错误而不是将其传递给 H2。
我们与这些客户共享了所有受影响版本的打补丁二进制文件,但保留了源代码。对于使用自定义分支的客户,我们要求他们直接与我们联系,并根据保密协议向他们提供了补丁。大约有二十个客户正在运行自定义分支并与我们取得了联系。
到目前为止一切顺利。
在接下来的三天里,发生了一些改变我们计划的事件。
等待和观察(7月21-27日)
我们收到了似乎来自客户的两份独立报告,声称他们已经知道该漏洞。当时,这非常令人惊讶,因为该漏洞已经存在了一年多才被发现,并且利用需要相当具体地利用我们产品中的一些漏洞。我们深入挖掘,了解到这两位客户都收到了发现该漏洞的原始研究团队的通知(两位客户都在 Metabase 安全问题上提供了漏洞赏金)。
这一点,加上自定义分支的数量,使我们非常担心在没有提前警告或可行的修复二进制文件的情况下,我们的开源社区会受到影响。因此,我们决定加快公告时间表,但仍然推迟披露实际的漏洞利用。
7月21日,我们发布了推文,更新了博客,在领英上发布了帖子,并向我们数万名订阅了更新邮件列表的用户发送了电子邮件。
我们开始监控社交媒体,看是否有新的情况出现,但几天来都没有任何相关信息,包括在 Hacker News 上。几天以来,人们对此有点兴趣,但没有任何关于有人反编译和重新创建漏洞利用的消息。
7月26日,我们收到了另一份安全报告(此后称为“Qing”漏洞),详细说明了如何通过在连接详细信息中使用单独的密钥(而不是全部在同一连接字符串中)绕过 /api/database
端点(该端点仅限于经过身份验证的管理员)上的控制,以及 AssetNote 漏洞的一个变体,该变体使用与外部数据库的连接来运行命令。
虽然这些发现令人不安,但它们需要已登录的管理员,只要部署了之前的打补丁二进制文件,就不可能发生未经身份验证的 RCE。
7月27日,一位工程师(此后称为“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 云客户缓解了我们自己服务器上的问题。一旦我们听说每个漏洞利用,我们就在所有相关的 API 端点上设置了网络级阻止,关闭了我们的商店以停止新的注册,并开始了审计我们拥有的所有日志的有趣过程。到周六深夜(7月29日),我们已经单独审计了所有对易受攻击的端点进行过调用的服务器,并且没有发现任何篡改证据。我们仍在密切关注任何可疑活动,并正在进行大量内部讨论,以从中吸取教训。
7月31日,我们向社区发送了另一条推文和领英帖子。
总而言之,我们通过混淆努力,在发布通用补丁版本和漏洞进入公众视野之间争取了将近一周的时间。虽然不理想,但希望能让更多人有时间升级。
目前情况
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 云客户,我们没有发现您受到任何影响。我们一收到报告,就立即修补并阻止了所有漏洞。我们 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 攻击的节点。
我们目前未发现上述端点存在任何其他漏洞。