# -*- coding: binary -*-
module Msf::Exploit::Remote::HTTP::Typo3::Login

  # performs a typo3 backend login
  #
  # @param user [String] Username
  # @param pass [String] Password
  # @return [String,nil] the session cookies as a single string on successful login, nil otherwise
  def typo3_backend_login(user, pass)
    # get login page for RSA modulus and exponent
    res_main = send_request_cgi({
      'method' => 'GET',
      'uri' => typo3_url_login
    })

    unless res_main and res_main.code == 200
      vprint_error('Can not reach login page')
      return nil
    end

    e_match = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)
    if e_match.nil?
      vprint_error('Can not find rsa_e value')
      return nil
    end
    e = e_match[1]

    n_match = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)
    if n_match.nil?
      vprint_error('Can not find rsa_n value')
      return nil
    end
    n = n_match[1]

    vprint_status("e: #{e}")
    vprint_status("n: #{n}")
    rsa_enc = typo3_helper_login_rsa(e, n, pass)
    vprint_status("RSA Hash: #{rsa_enc}")
    # make login request
    vars_post = {
      'n' => '',
      'e' => '',
      'login_status' => 'login',
      'userident' => rsa_enc,
      'redirect_url' => 'backend.php',
      'loginRefresh' => '',
      'interface' => 'backend',
      'username' => user,
      'p_field' => '',
      'commandLI' => 'Login'
    }
    res_login = send_request_cgi({
      'method' => 'POST',
      'uri' => typo3_url_login,
      'cookie' => res_main.get_cookies,
      'vars_post' => vars_post,
      'headers' => {'Referer' => full_uri}
    })
    if res_login
      if res_login.body =~ /<!-- ###LOGIN_ERROR### begin -->(.*)<!-- ###LOGIN_ERROR### end -->/im
        vprint_status(strip_tags($1))
        return nil
      elsif res_login.body =~ /<p class="t3-error-text">(.*?)<\/p>/im
        vprint_status(strip_tags($1))
        return nil
      else
        cookies = res_login.get_cookies
        return cookies if typo3_admin_cookie_valid?(cookies)
        return nil
      end
    end

    return nil
  end

  # verifies cookies by calling the backend and checking the response
  #
  # @param cookiestring [String] The http cookies as a concatenated string
  # @return [Boolean] true if the cookie is valid, false otherwise
  def typo3_admin_cookie_valid?(cookiestring)
    res_check = send_request_cgi({
      'method' => 'GET',
      'uri' => typo3_url_backend,
      'cookie' => cookiestring,
      'headers' => {'Referer' => full_uri}
    })
    return true if res_check and res_check.code == 200 and res_check.body and res_check.body =~ /<body [^>]+ id="typo3-backend-php">/
    return false
  end

  private

  # encrypts the password with the public key for login
  #
  # @param e [String] The exponent extracted from the login page
  # @param n [String] The modulus extracted from the login page
  # @param password [String] The clear text password to encrypt
  # @return [String] the base64 encoded password with prefixed 'rsa:'
  def typo3_helper_login_rsa(e, n, password)
    # OpenSSL 3.0+
    if OpenSSL::PKey.respond_to?(:generate_key)
      exponent = OpenSSL::BN.new e.hex
      modulus = OpenSSL::BN.new n.hex
      asn1 = OpenSSL::ASN1::Sequence(
        [
          OpenSSL::ASN1::Integer(modulus),
          OpenSSL::ASN1::Integer(exponent),
        ]
      )
      key = OpenSSL::PKey::RSA.new(asn1.to_der)
    else
      key = OpenSSL::PKey::RSA.new
      exponent = OpenSSL::BN.new e.hex.to_s
      modulus = OpenSSL::BN.new n.hex.to_s
      if key.respond_to?(:set_key)
        # Ruby 2.4+
        key.set_key(modulus, exponent, nil)
      else
        key.e = exponent
        key.n = modulus
      end
    end
    enc = key.public_encrypt(password)
    enc_b64 = Rex::Text.encode_base64(enc)
    "rsa:#{enc_b64}"
  end

end
