Pentera

Automate remediation actions based on Pentera, the Automated Security Validation Platform, proactively exposing high-risk vulnerabilities.

Data Enrichment & Threat Intelligence · Pentera

Details

IDPentera
ProviderPentera
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/auth-utils:1.0.0.3562326
Supported ModulesAgentix XSIAM

README

Overview

Integration with Pentera.
This integration was integrated and tested with version 4.5.2 of Pentera

Pentera Playbook

Use Cases

Integration Use Cases:

  1. Integrate PenTera’s Automated Penetration Testing findings within Cortex XSOAR for playbook-driven enrichment and response
  2. Address penetration testing findings, prioritize, and automate response tasks
  3. Leverage Cortex XSOAR’s third-party product integrations

Use Case #1: Automate Dynamic Vulnerability Alerts - Password Policy
Challenge: Password policies are a continuous undertaking that organizations need to review regularly.
Solution: With the Cortex XSOAR-PenTera integration, PenTera can continuously validate the effectiveness of enterprise passwords and take action on easily crackable passwords with focus on high privileged accounts. Once PenTera flags a password that doesn’t meet the standard, automated playbooks through Cortex XSOAR take action and remediate the vulnerability based on corporate policy.

Use Case #2: Automated real-time validation for critical vulnerabilities
Challenge: Continuous security validation is critical for the ongoing cyber hygiene of an organization’s network. However, critical vulnerabilities require on-demand testing as they influence many components of the network. Security teams struggle with prioritizing remediation and understanding the true impact vulnerabilities have on their specific network.
Solution: After running automated single-action tests for critical vulnerabilities, the Cortex XSOAR integration allows security teams to automate the response process based on the findings. For example, PenTera discovers the vulnerability of different components of the network, e.g a server or an endpoint. The latter is a simpler fix that should go through one workflow, perhaps even be automatically remediated, while the first, a much more complex process, will create a high-risk task in the relevant workflow, automatically prioritizing the response tasks based on business impact severity.

Configure Pentera on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Pentera.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Server URL (e.g. https://192.168.64.128)
    • Pentera API port
    • TGT (The token from Pentera UI in Administration -> API Clients)
    • Client Id
    • Trust any certificate (not secure)
    • Use system proxy settings
  4. Click Test to validate the URLs, token, and connection.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

  1. pentera-run-template-by-name
  2. pentera-get-task-run-status
  3. pentera-get-task-run-full-action-report

1. pentera-run-template-by-name

Run a specific template by its name. Please add the template name in the parameters

Required Permissions

Operator and admin users

Base Command

pentera-run-template-by-name

Input
Argument Name Description Required
template_name The name of the template that you want to run Required
Context Output
Path Type Description
Pentera.TaskRun.TemplateName String Returns the name of the template
Pentera.TaskRun.ID String The task run id
Pentera.TaskRun.StartTime Date The date when the task run started
Pentera.TaskRun.EndTime Date The date when the task run ended
Pentera.TaskRun.Status String The status of the task run; e.g.: ‘Running’, ‘Pending’, ‘Failed’, ‘Cleaning up’, ‘Canceled’, ‘Done’, ‘Warning’, ‘Aborted (exceeded max hosts limit)’.
Command Example

!pentera-run-template-by-name template_name="Test Template for Playbook"

Context Example
{
    "Pentera.TaskRun": {
        "Status": "Running", 
        "TemplateName": "Test Template for Playbook", 
        "StartTime": 2020-02-13T19:32:45Z, 
        "EndTime": null, 
        "ID": "5e45883d1deb8eda82b1eed5"
    }
}
Human Readable Output

Test Template for Playbook

|ID|StartTime|Status|TemplateName|
|—|—|—|—|
| 2020-02-13 17:32:45Z | 5e45883d1deb8eda82b1eed5 | ‘2020-02-13T17:32:45Z’ | Running | Test Template for Playbook |
Integration log: Full Integration Log:
Got command: pentera-run-template-by-name
result is JSON
Parsed JSON Response: {‘ID’: ‘5e45883d1deb8eda82b1eed5’, ‘TemplateName’: ‘Test Template for Playbook’, ‘StartTime’: ‘2020-02-13T17:32:45Z’, ‘EndTime’: None, ‘Status’: ‘Running’}
Parsed JSON Response: {‘ID’: ‘5e45883d1deb8eda82b1eed5’, ‘TemplateName’: ‘Test Template for Playbook’, ‘StartTime’: ‘2020-02-13T17:32:45Z’, ‘EndTime’: None, ‘Status’: ‘Running’}

2. pentera-get-task-run-status

Get the status of a task run by its task run id

Required Permissions

Operator and admin users

Base Command

pentera-get-task-run-status

Input
Argument Name Description Required
task_run_id The ID of the task run Required
Context Output
Path Type Description
Pentera.TaskRun.ID String The task run id
Pentera.TaskRun.TemplateName String Returns the name of the template
Pentera.TaskRun.StartTime Date The date when the task run started
Pentera.TaskRun.EndTime Date The date when the task run ended
Pentera.TaskRun.Status String The status of the task run; e.g.: ‘Running’, ‘Pending’, ‘Failed’, ‘Cleaning up’, ‘Canceled’, ‘Done’, ‘Warning’, ‘Aborted (exceeded max hosts limit)’.
Command Example

```!pentera-get-task-run-status task_run_id=”5e4583221deb8eda82b195c5”


##### Context Example

{
“Pentera.TaskRun”: {
“Status”: “Done”,
“TemplateName”: “Test Template for Playbook”,
“StartTime”: “2020-02-13T17:10:58Z”,
“EndTime”: “2020-02-13T19:14:12Z”,
“ID”: “5e4583221deb8eda82b195c5”
}
}


##### Human Readable Output

### Test Template for Playbook

|EndTime|ID|StartTime|Status|TemplateName|
|---|---|---|---|---|
| 2020-02-13 17:10:58Z | 1581614052321.0 | 5e4583221deb8eda82b195c5 | 1581613858961.0 | Done | Test Template for Playbook |
Integration log: Full Integration Log:
Got command: pentera-get-task-run-status
result is JSON
Parsed JSON Response: {'ID': '5e4583221deb8eda82b195c5', 'TemplateName': 'Test Template for Playbook', 'StartTime': '2020-02-13T17:10:58Z', 'EndTime': '2020-02-13T19:14:12Z', 'Status': 'Done'}

### 3. pentera-get-task-run-full-action-report

Get the full action report of a task run

###### Fieldnames: 'Severity', 'Time', 'Duration', 'Operation Type', 'Techniques', 'Parameters', 'Status'

#### Severity

* Low: [0: 2.5)
* Medium: [2.5: 5)
* High: [5: 7.5)
* Critical: [7.5: 10]

#### Duration

In milliseconds

#### Status

'Running', 'Pending', 'Failed', 'Cleaning up', 'Canceled', 'Done', 'Warning', 'Aborted (exceeded max hosts limit)'.

##### Required Permissions

User view, operator and admin users

##### Base Command

`pentera-get-task-run-full-action-report`

##### Input

| __Argument Name__ | __Description__ | __Required__ |
| --- | --- | --- |
| task_run_id | The ID of the task run | Required |

##### Context Output

| __Path__ | __Type__ | __Description__ |
| --- | --- | --- |
| Pentera.TaskRun.ID | String | The task run id |
| Pentera.TaskRun.TemplateName | String | Returns the name of the template |
| Pentera.TaskRun.StartTime | Date | The date when the task run started |
| Pentera.TaskRun.EndTime | Date | The date when the task run ended |
| Pentera.TaskRun.Status | String | The status of the task run; e.g.: 'Running', 'Pending', 'Failed', 'Cleaning up', 'Canceled', 'Done', 'Warning', 'Aborted (exceeded max hosts limit)'. |
| Pentera.TaskRun.FullActionReport | String | The full action report of the task run |

##### Command Example

```!pentera-get-task-run-full-action-report task_run_id="5e4583221deb8eda82b195c5"
Context Example
{
    "Pentera.TaskRun": {
        "FullActionReport": [
            {
                "Status": "no results", 
                "Severity": "", 
                "Parameters": "Host: 192.168.1.2", 
                "Time": "13/02/2020, 17:11:59", 
                "Duration": "31578", 
                "Operation Type": "BlueKeep (CVE-2019-0708) Vulnerability Discovery", 
                "Techniques": "Network Service Scanning(T1046)"
            }, 
            {
                "Status": "no results", 
                "Severity": "", 
                "Parameters": "Host: 192.168.1.1", 
                "Time": "13/02/2020, 17:12:01", 
                "Duration": "31618", 
                "Operation Type": "BlueKeep (CVE-2019-0708) Vulnerability Discovery", 
                "Techniques": "Network Service Scanning(T1046)"
            }
        ], 
        "ID": "5e4583221deb8eda82b195c5"
    }
}
Human Readable Output

Pentera Report for TaskRun ID

|Agent Name|Categories|Duration|Operation Type|Parameters|Severity|Status|Techniques|Time|
|—|—|—|—|—|—|—|—|—|
| default-node | Discovery, Reconnaissance | 31578 | BlueKeep (CVE-2019-0708) Vulnerability Discovery | Host: 192.168.1.2 | | no results | Network Service Scanning(T1046) | 13/02/2020, 17:11:59 |
| default-node | Discovery, Reconnaissance | 31618 | BlueKeep (CVE-2019-0708) Vulnerability Discovery | Host: 192.168.1.1 | | no results | Network Service Scanning(T1046) | 13/02/2020, 17:12:01 |
Integration log: Full Integration Log:
Got command: pentera-get-task-run-full-action-report
result is TEXT

Configuration parameters

  • url — Server URL (e.g. https://192.168.64.128) (required)
  • port — Pentera API port (required)
  • clientId — Client Id (required)
  • tgt — TGT (The token from Pentera UI in Administration -> API Clients) (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (3)

  • pentera-get-task-run-full-action-report

    Get the full action report of a task run.

  • pentera-get-task-run-status

    Get the status of a task run by its task run id.

  • pentera-run-template-by-name

    Run a specific template by its name. Please add the template name in the parameters.

import csv
import io
import json
from enum import Enum

import demistomock as demisto
import jwt
import requests

# Disable insecure warnings
import urllib3
from CommonServerPython import *

from CommonServerUserPython import *

urllib3.disable_warnings()

HEADERS = {"Accept": "application/json"}
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

HEALTH_URL_SUFFIX = "/health/dbChecks"
AUTH_URL_SUFFIX = "/auth/token"
LIST_TEMPLATES_URL_SUFFIX = "/api/v1/templates"
CANCEL_TASK_URL_SUFFIX = "/api/v1/taskRun/{taskRunId}/cancel"
GET_TASK_RUN_STATUS_URL_SUFFIX = "/api/v1/taskRun/{taskRunId}"
RUN_BULK_URL_SUFFIX = "/api/v1/template/runBulk"
EXPORT_CSV_URL_SUFFIX = "/api/v1/taskRun/{taskRunId}/fullActionReportCSV"


class Request(Enum):
    POST = "POST"
    GET = "GET"


class AuthorizationError(Exception):
    pass


class Client(BaseClient):
    def __init__(self, base_url: str, tgt: str, client_id: str, verify: bool, proxy: bool, headers):
        super().__init__(base_url=f"{base_url}", headers=headers, verify=verify, proxy=proxy)
        self.session = requests.Session()
        self.session.headers = headers
        self.client_id = client_id
        self.tgt = tgt
        self.access_token = ""
        self.expiry = 0
        self.load_session_parameters()

    def load_session_parameters(self):
        context: dict = get_integration_context()
        if context and context["base_url"] == self._base_url:
            self.tgt = context["tgt"]
            self.access_token = context["accessToken"]
            self.expiry = context["expiry"]

    def generic_request(
        self,
        method: str,
        url_suffix: str = None,
        full_url: str = None,
        headers: dict = None,
        params: dict = None,
        data: dict = None,
        response_type: str = "json",
    ):
        full_url = full_url if full_url else f"{self._base_url}{url_suffix}"
        headers = headers if headers else self._headers
        try:
            res = self.session.request(method, full_url, headers=headers, verify=self._verify, data=data, params=params)
            demisto.debug(f"Got response: {res}")
            if not res.ok:
                status_code = res.status_code
                if status_code == requests.status_codes.codes.UNAUTHORIZED:  # pylint: disable=no-member
                    info = "Check that your system clock is set to the correct date and time before you try again."
                    raise AuthorizationError(f"Status code: {status_code}, reason: {res.text}. {info}")
                raise ValueError(f"Error in API call to Pentera. Status code: {status_code}, reason: {res.text}")

            try:
                if response_type == "json":
                    demisto.debug("result is JSON")
                    return res.json()
                demisto.debug("result is TEXT")
                return res.text
            except Exception:
                raise ValueError(f"Failed to parse http response to JSON format. Original response body: \n{res.text}")

        except requests.exceptions.ConnectTimeout as exception:
            err_msg = (
                "Connection Timeout Error - potential reasons might be that the Server URL parameter"
                " is incorrect or that the Server is not accessible from your host."
            )
            raise DemistoException(err_msg, exception)

        except requests.exceptions.SSLError as exception:
            err_msg = (
                "SSL Certificate Verification Failed - try selecting 'Trust any certificate' checkbox in"
                " the integration configuration."
            )
            raise DemistoException(err_msg, exception)

        except requests.exceptions.ProxyError as exception:
            err_msg = (
                "Proxy Error - if the 'Use system proxy' checkbox in the integration configuration is"
                " selected, try clearing the checkbox."
            )
            raise DemistoException(err_msg, exception)

        except requests.exceptions.ConnectionError as exception:
            err_msg = getattr(exception, "message", str(exception))
            raise DemistoException(err_msg, exception)

        except Exception as request_error:
            message = getattr(request_error, "message", str(request_error))
            raise DemistoException(f"Could not send request to Pentera, reason: {message}", exception=request_error)

    def authenticate(self):
        data = {"client_id": self.client_id, "tgt": self.tgt}
        res = self.generic_request(method=Request.POST.value, url_suffix=AUTH_URL_SUFFIX, data=data)
        self.tgt = res.get("tgt")
        self.access_token = res.get("token")
        jwt_decode_dict = jwt.get_unverified_header(self.access_token)
        self.expiry = jwt_decode_dict.get("exp", 0) if jwt_decode_dict else 0
        self.save_session_parameters()

    def save_session_parameters(self):
        context = {"base_url": self._base_url, "tgt": self.tgt, "accessToken": self.access_token, "expiry": self.expiry}
        set_integration_context(context)

    def is_access_token_valid(self):
        if not self.access_token or not self.expiry or self.expiry < int(datetime.utcnow().timestamp()):  # noqa: SIM103
            return False
        return True

    def create_basic_authentication_header(self):
        authentication_headers = HEADERS.copy()
        token = self.access_token + ":"
        encoded_bytes = base64.b64encode(token.encode("utf-8"))
        encoded_str = str(encoded_bytes, "utf-8")
        authentication_headers["Authorization"] = "Basic " + encoded_str
        return authentication_headers

    def run_health_checks(self):
        res = self.generic_request(method=Request.GET.value, url_suffix=HEALTH_URL_SUFFIX)
        return res

    def run_template_by_name(self, template_name):
        headers = self.create_basic_authentication_header()
        data = {"templateNames": [template_name]}
        res = self.generic_request(method=Request.POST.value, url_suffix=RUN_BULK_URL_SUFFIX, headers=headers, data=data)
        return res

    def get_task_run_status_by_task_run_id(self, task_run_id: str):
        headers = self.create_basic_authentication_header()
        url_suffix = GET_TASK_RUN_STATUS_URL_SUFFIX.format(taskRunId=task_run_id)
        res = self.generic_request(method=Request.GET.value, url_suffix=url_suffix, headers=headers, data={})
        task_status = res.get("taskRuns")[0]
        return task_status

    def get_task_run_full_action_report_by_task_run_id(self, task_run_id: str):
        headers = self.create_basic_authentication_header()
        url_suffix = EXPORT_CSV_URL_SUFFIX.format(taskRunId=task_run_id)
        res = self.generic_request(method=Request.GET.value, url_suffix=url_suffix, headers=headers, response_type="csv")
        return res


def pentera_test_module_command(client: Client):
    try:
        response = client.run_health_checks()
    except Exception as test_error:
        message = getattr(test_error, "message", str(test_error))
        raise DemistoException(message)
    exceptions: list = response.get("exceptions")
    if exceptions:
        raise DemistoException(", ".join(exceptions))
    return "ok"


def pentera_run_template_command(client: Client, args):
    template_name = args.get("template_name")
    try:
        response = client.run_template_by_name(template_name)
        task_run_json = response.get("taskRuns")[0]
        parsed_response = parse_task_run_status(task_run_json)
        readable_output = tableToMarkdown(template_name, parse_task_run_status(task_run_json), removeNull=True)
        return (
            readable_output,
            {"Pentera.TaskRun(val.ID == obj.ID)": parsed_response},
            response,  # raw response - the original response
        )

    except Exception as run_template_error:
        message = getattr(run_template_error, "message", str(run_template_error))
        raise DemistoException(
            f"Could not run template with template_name: '{template_name}', reason: {message}", exception=run_template_error
        )


def pentera_get_task_run_status_command(client: Client, args):
    task_run_id = args.get("task_run_id")
    try:
        task_run_status = client.get_task_run_status_by_task_run_id(task_run_id)
        parsed_response = parse_task_run_status(task_run_status)
        title = parsed_response["TemplateName"] + ": " + parsed_response["Status"]
        readable_output = tableToMarkdown(title, parsed_response, removeNull=True)
        return (
            readable_output,
            {"Pentera.TaskRun(val.ID == obj.ID)": parsed_response},
            task_run_status,  # raw response - the original response
        )
    except Exception as status_error:
        message = getattr(status_error, "message", str(status_error))
        raise DemistoException(
            f"Could not get task run status for task_run_id: '{task_run_id}', reason: {message}", exception=status_error
        )


def pentera_get_task_run_full_action_report_command(client: Client, args):
    def _convert_csv_file_to_dict(csv_file):
        def _map_parameters_string_to_object(str_parameters: str = None):
            if str_parameters:
                return json.loads(str_parameters)
            return None

        csv_reader = csv.DictReader(io.StringIO(csv_file))
        data = []
        for row in csv_reader:
            row_copy = row.copy()
            converted_params = _map_parameters_string_to_object(row_copy.get("Parameters"))
            if converted_params:
                row_copy["Parameters"] = converted_params
            data.append(row_copy)
        return data

    def _convert_full_action_report_time(full_action_report_list: list[dict]):
        def _parse_date(full_date, separator):
            if isinstance(full_date, str) and isinstance(separator, str):
                date = full_date.split(separator)
                if len(date) > 2:
                    first_arg = date[0]
                    second_arg = date[1]
                    third_arg = date[2]
                    return first_arg, second_arg, third_arg
                return None
            return None

        res_list: list[dict] = []
        for ordered_dict in full_action_report_list:
            full_date_to_convert = ordered_dict["Time"]
            full_date_list = full_date_to_convert.split(" ")
            year, month, day = _parse_date(full_date_list[0], "-")
            hours, minutes, seconds = _parse_date(full_date_list[1], ":")
            converted_date = year + "-" + month + "-" + day + "T" + hours + ":" + minutes + ":" + seconds + "Z"
            new_ordered_dict = ordered_dict.copy()
            new_ordered_dict["Time"] = converted_date
            res_list.append(new_ordered_dict)
        return res_list

    entries = []
    task_run_id = args.get("task_run_id")
    try:
        response_csv = client.get_task_run_full_action_report_by_task_run_id(task_run_id)
        readable_output = f"# Pentera Report for TaskRun ID {task_run_id}"
        entry = fileResult(f"penterascan-{task_run_id}.csv", response_csv, entryTypes["entryInfoFile"])
        entry["HumanReadable"] = readable_output
        entry["ContentsFormat"] = formats["markdown"]
        entries.append(entry)
        csv_dict = _convert_csv_file_to_dict(response_csv)
        date_converted_csv_dict = _convert_full_action_report_time(csv_dict)
        human_readable = tableToMarkdown(readable_output, date_converted_csv_dict)
        entries.append(
            {
                "Type": entryTypes["note"],
                "ContentsFormat": formats["json"],
                "ReadableContentsFormat": formats["markdown"],
                "Contents": date_converted_csv_dict,
                "EntryContext": {
                    "Pentera.TaskRun(val.ID == obj.ID)": {"FullActionReport": date_converted_csv_dict, "ID": task_run_id}
                },
                "HumanReadable": human_readable,
            }
        )
        return entries
    except Exception as report_error:
        message = getattr(report_error, "message", str(report_error))
        raise DemistoException(
            f"Could not get full action report for task_run_id: '{task_run_id}', reason: {message}", exception=report_error
        )


def parse_task_run_status(json_response):
    def _convert_time_in_millis_to_date_format(time_in_millis):
        time_in_date_format = None
        try:
            time_in_date_format = datetime.fromtimestamp(float(time_in_millis) / 1000).strftime(DATE_FORMAT)
            return time_in_date_format
        except TypeError:
            return time_in_date_format

    if isinstance(json_response, dict):
        end_time_date_format = _convert_time_in_millis_to_date_format(json_response.get("endTime"))
        start_time_date_format = _convert_time_in_millis_to_date_format(json_response.get("startTime"))
        parsed_json_response = {
            "ID": json_response.get("taskRunId"),
            "TemplateName": json_response.get("taskRunName"),
            "StartTime": start_time_date_format,
            "EndTime": end_time_date_format,
            "Status": json_response.get("status"),
        }
        return parsed_json_response
    return None


def pentera_authentication(client: Client):
    if not client.is_access_token_valid():
        try:
            client.authenticate()
        except Exception as auth_error:
            message = getattr(auth_error, "message", str(auth_error))
            raise DemistoException(f"Could not authenticate to Pentera, reason: {message}", exception=auth_error)


def increase_csv_field_size_limit():
    """
    This method will try to increase the csv field size limit as files might contain huge fields.
    :return: None
    """
    try:
        csv.field_size_limit(sys.maxsize)
    except OverflowError:
        pass


def main():
    params: dict = demisto.params()
    application_port = params["port"]
    base_url = params["url"].rstrip("/") + ":" + application_port
    client_id = params["clientId"]
    tgt = params["tgt"]
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)
    client = Client(base_url=base_url, tgt=tgt, verify=verify_certificate, client_id=client_id, proxy=proxy, headers=HEADERS)
    command = demisto.command()
    demisto.debug(f"Got command: {command}")
    try:
        if demisto.command() == "test-module":
            demisto.results(pentera_test_module_command(client))
        else:
            pentera_authentication(client)
            if demisto.command() == "pentera-run-template-by-name":
                return_outputs(*pentera_run_template_command(client, demisto.args()))
            elif demisto.command() == "pentera-get-task-run-status":
                return_outputs(*pentera_get_task_run_status_command(client, demisto.args()))
            elif demisto.command() == "pentera-get-task-run-full-action-report":
                demisto.results(pentera_get_task_run_full_action_report_command(client, demisto.args()))
    except Exception as e:
        return_error(f'Failed to execute command: {command}, {getattr(e, "message", str(e))}', error=e)


if __name__ in ("__main__", "__builtin__", "builtins"):
    increase_csv_field_size_limit()
    main()