"""
Kibana CVE-2019-7609 test

This module implements a test to see if the host is vulnerable to CVE-2019-7609

https://nvd.nist.gov/vuln/detail/CVE-2019-7609

Contains:
- Vuln class for performing the test
- run() function as an entry point for running the test
"""

from http import HTTPStatus
import requests
from ptlibs.ptprinthelper import ptprint
from packaging.version import Version
from pathlib import Path
from json import dumps


__TESTLABEL__ = "Kibana CVE-2019-7609 test"


class Vuln:
    """
    This class check to see if the host is vulnerable to CVE-2019-7609 by first checking if the Kibana version is vulnerable
    and then trying to exploit the vulnerability
    """
    def __init__(self, args: object, ptjsonlib: object, helpers: object, http_client: object, base_response: object) -> None:
        self.args = args
        self.ptjsonlib = ptjsonlib
        self.helpers = helpers
        self.http_client = http_client
        self.base_response = base_response
        self.helpers.print_header(__TESTLABEL__)
        self.cve_id = Path(__file__).stem.upper()


    def _check_version(self, provided_version: str) -> None:
        """
        This method compares the version of Kibana running on the host to check if it is < 5.6.15 or > 5.6.15 and < 6.6.1 and thus vulnerable to CVE-2019-7609
        """
        version = Version(provided_version)

        if version < Version("5.6.15") or Version("5.6.15") < version < Version("6.6.1"):
            ptprint(f"Kibana {version} should be vulnerable to {self.cve_id}", "INFO", not self.args.json, indent=4)
            return

        ptprint(f"Kibana {version} might not be vulnerable to {self.cve_id}", "INFO", not self.args.json, indent=4)

    def _get_kbn_version(self) -> str:
        """
        This method retrieves the Kibana version from the /api/status endpoint
        """
        try:
            response = self.http_client.send_request(url=self.args.url+"api/status", method="GET", headers=self.args.headers, follow_redirects=False)
        except requests.exceptions.RequestException as error_msg:
            ptprint(f"Error retrieving response from /api/status. Response:\n{error_msg}", "ERROR", not self.args.json, indent=4)
            return ""

        if response.status_code != HTTPStatus.OK:
            ptprint(f"Error retrieving response from /api/status. Response code:\n{response.status_code}", "ERROR", not self.args.json,
                    indent=4)
            return ""

        try:
            response = response.json()
        except ValueError as error_msg:
            ptprint(f"Error communicating with API: {error_msg}", "ERROR", not self.args.json, indent=4)
            return ""

        version = response.get("version", {})

        if type(version) == dict:
            version = version.get("number", "")

        if version:
            return version
        else:
            ptprint("Could not enumerate version", "OK", not self.args.json, indent=4)
            return ""


    def _exploit(self) -> bool:
        """
        This method verifies if the vulnerability is exploitable by sending a POST request to the timelion API endpoint and
        seeing if we get an HTTP 200 OK response

        The exploit is based on a PoC by LandGrey available at https://github.com/LandGrey/CVE-2019-7609

        :return: True if we get an HTTP 200 OK response. False otherwise
        """
        payload = '{"sheet":[".es(*)"],"time":{"from":"now-1m","to":"now","mode":"quick","interval":"auto","timezone":"Europe/Prague"}}'
        headers = self.args.headers.copy()
        headers.update({"kbn-xsrf": "true", "Content-Type": "application/json"})
        response = self.http_client.send_request(self.args.url+"/api/timelion/run", method="POST", headers=headers, data=payload)

        if response.status_code == 200 and '"failed":true' not in response.text:
            try:
                response = response.json()
            except ValueError as e:
                ptprint(f"Could not get JSON from response: {e}", "OK", not self.args.json, indent=4)
                ptprint(f"Got response: {response.text}", "ADDITIONS", not self.args.json, indent=4, colortext=True)
                return False

            ptprint(f"Exploit successful! Got response:\n {dumps(response, indent=4)}", "ADDITIONS",
                    self.args.verbose, indent=4, colortext=True)
            return True

        ptprint(f"Error exploiting CVE-2019-7609. Got response:\n {dumps(response, indent=4)}", "ADDITIONS", self.args.verbose,
                indent=4, colortext=True)
        return False


    def run(self) -> None:
        """
        Executes the Kibana CVE-2019-7609 test by first checking if the version might be vulnerable and then exploiting
        the vulnerability with the _exploit() method. If the exploit is successful, CVE-2019-7609 is added to the JSON output
        """
        if not (version := self._get_kbn_version()):
            ptprint(f"The host is not vulnerable to {self.cve_id}", "OK", not self.args.json, indent=4)
            return

        self._check_version(version)

        if self._exploit():
            ptprint(f"The host is vulnerable to {self.cve_id}", "VULN", not self.args.json, indent=4)
            es_node_key = self.helpers.check_node("swKibana")
            if es_node_key:
                self.ptjsonlib.add_vulnerability(f"PTV-{self.cve_id}", node_key=es_node_key)
            else:
                self.ptjsonlib.add_vulnerability(f"PTV-{self.cve_id}")

        else:
            ptprint(f"The host is not vulnerable to {self.cve_id}", "OK", not self.args.json, indent=4)


def run(args, ptjsonlib, helpers, http_client, base_response):
    """Entry point for running the Vuln test"""
    Vuln(args, ptjsonlib, helpers, http_client, base_response).run()
