‧
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)。 - 您可以欺骗 Metabase 将数据库连接视为 PostgreSQL 连接,但在详细信息有效负载中偷偷放入 H2 连接字符串,让 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 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 下向他们提供了补丁。大约有 20 位客户运行自定义分支并与我们联系。
到目前为止一切顺利。
在接下来的三天里,发生了一些事件,改变了我们的计划。
等待和观察(7 月 21 日至 27 日)
我们似乎收到了来自客户的两份独立报告,称他们已经知道该漏洞。当时,这非常令人惊讶,因为该漏洞在被发现之前已经存在了一年多,并且利用漏洞需要相当具体地使用我们产品中的许多漏洞。我们深入挖掘了一下,了解到两位客户都已收到发现该漏洞的原始研究团队的通知(两位客户都为 Metabase 安全问题提供漏洞赏金)。
再加上自定义分支的数量,这让我们非常担心在没有提前警告或可行的已修复二进制文件的情况下离开我们的 OSS 社区。因此,我们决定加快我们的公告时间表,但仍暂缓披露实际的漏洞利用程序。
7 月 21 日,我们在 Twitter 上发布了推文,更新了我们的博客,在 LinkedIn 上发布了帖子,并向我们的整个更新邮件列表发送了一封电子邮件,数万人都已注册。
我们开始监控社交媒体,看看是否出现什么情况,但在几天内没有任何相关信息,包括在 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 Cloud 客户缓解了我们自己服务器上的问题。一旦我们听到每个漏洞利用程序,我们就在所有相关 API 端点上设置了网络级阻止,关闭了我们的商店以进行新的注册,并开始了审核我们拥有的每一点日志的有趣过程。到星期六深夜(7 月 29 日),我们已经单独审核了每个有易受攻击端点调用的服务器,并且没有发现任何篡改证据。我们仍在密切关注任何可疑活动,并正在进行许多内部对话,讨论从中吸取什么教训。
7 月 31 日,我们向社区发布了另一条推文和 Linkedin 帖子。
总而言之,我们设法在我们发布通用补丁版本和漏洞进入大众视野之间,通过我们的混淆工作争取了将近一周的时间。虽然不理想,但希望这能让更多人有时间升级。
当前情况
TL;DR:如果您是自托管用户,并且上次升级是在 2023 年 7 月 28 日之前,请立即升级。
如果您是 Metabase Cloud 客户,则不受影响。
如果您是自托管用户,并且正在运行最新的二进制文件(此时为 46.6.4),则您是安全的。
如果您使用的是 43-45 版本的 Metabase,并且尚未升级到最新的次要版本 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
,并看到一个 bash 进程正在运行,并带有参数(例如 echo
、编码参数等),则该实例已打开反向 shell(例如,以下屏幕截图中的 PID 3763
)。
如果您在运行 Metabase 的服务器或容器中运行 netstat -antp
,并看到不属于 Metabase 使用的端口的活动连接,并且连接具有 ESTABLISHED
状态(例如,以下屏幕截图中来自外部地址 172.23.0.2:9001
的连接)。
如果 shell 已退出一段时间,您可能还会看到状态为 FIN_WAIT2
的连接,如本例所示
我应该对此做些什么?
如果您怀疑您可能受到了影响,我们建议您
- 使用新的服务器实例将您的实例升级到最新的二进制文件。
- 轮换您的数据仓库凭据。
- 限制未知主机对您的数据仓库的 IP 访问。
- 检查您的 Metabase 实例上是否没有不熟悉的用户帐户。
- 轮换您的 Metabase 中使用的 Slack 和 SMTP 凭据令牌。
此漏洞正在被积极利用
我们意识到一些恶意软件开发者/僵尸网络已在其攻击中包含了漏洞利用代码。我们还至少知道一个攻击 Metabase 服务器以挖掘加密货币并将服务器用作 DDoS 攻击节点的实例。
我们不知道提到的端点中还有任何其他漏洞。