BotDefender

Gathers PerimeterX related data.

Data Enrichment & Threat Intelligence · PerimeterX

Details

IDBotDefender
ProviderHUMAN
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Gathers PerimeterX related data

Configure BotDefender in Cortex

Parameter Description Required
url Server URL (e.g. https://example.net) True
apikey API Key True
incidentType Incident type False
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False

Commands

You can execute these commands from the 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.

ip


Gets the PerimeterX DBotScore decision for a particular IP

Base Command

ip

Input

Argument Name Description Required
ip The custom parameter value or IP address for which the report is requested Optional

Context Output

Path Type Description
IP.Address String Bad IP address
IP.Malicious.Vendor String For malicious IPs, the vendor that made the decision
IP.Malicious.Description String For malicious IPs, the reason that the vendor made the decision
DBotScore.Indicator String The indicator that was tested
DBotScore.Type String The indicator type
DBotScore.Vendor String The vendor used to calculate the score
DBotScore.Score Number The actual score

Command Example

!ip ip="5.79.76.181"

Context Example

{
    "DBotScore": {
        "Indicator": "5.79.76.181",
        "Score": 3,
        "Type": "ip",
        "Vendor": "PerimeterX"
    },
    "IP": {
        "Address": "5.79.76.181",
        "Malicious": {
            "Description": "Something random from PerimeterX for now enjoy!",
            "Vendor": "PerimeterX"
        }
    },
    "PerimeterX": {
        "catpchaSolves": 200,
        "ipClassifications": [
            {
                "class": "Bad Reputation",
                "name": "Bad Reputation"
            },
            {
                "class": "SharedIPs",
                "name": "Shared IPs"
            },
            {
                "class": "DataCenter",
                "name": "TAG DCIP"
            }
        ],
        "max_risk_score": 100,
        "pageTypeDistributions": [
            {
                "count": 1228,
                "pageType": "Login"
            },
            {
                "count": 739,
                "pageType": "Scraping"
            },
            {
                "count": 139,
                "pageType": "Checkout"
            }
        ],
        "topBlockedURLPaths": [
            {
                "blockedURLPath": "/",
                "count": 1404
            },
            {
                "blockedURLPath": "/cgi-bin/way-board.cgi",
                "count": 702
            },
            {
                "blockedURLPath": "/loginok/light.cgi",
                "count": 702
            }
        ],
        "topIncidentTypes": [
            {
                "count": 2106,
                "incidentType": "Spoof"
            },
            {
                "count": 702,
                "incidentType": "Bot Behavior"
            }
        ],
        "topURLPaths": [
            {
                "count": 3315,
                "urlPath": "/favicon.ico"
            },
            {
                "count": 3253,
                "urlPath": "/favicon.png"
            },
            {
                "count": 3212,
                "urlPath": "/"
            },
            {
                "count": 1228,
                "urlPath": "/loginok/light.cgi"
            },
            {
                "count": 1222,
                "urlPath": "/cgi-bin/way-board.cgi"
            },
            {
                "count": 205,
                "urlPath": "/phpmyadmin/"
            },
            {
                "count": 139,
                "urlPath": "-"
            },
            {
                "count": 82,
                "urlPath": "/images/icons/favicon.ico"
            },
            {
                "count": 48,
                "urlPath": "/test.php"
            }
        ],
        "topUserAgents": [
            {
                "count": 84,
                "userAgentName": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.79 Safari/537.36"
            },
            {
                "count": 80,
                "userAgentName": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
            },
            {
                "count": 78,
                "userAgentName": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36 OPR/53.0.2907.99"
            },
            {
                "count": 76,
                "userAgentName": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.64 (Edition Yx)"
            },
            {
                "count": 72,
                "userAgentName": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.51"
            },
            {
                "count": 72,
                "userAgentName": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36"
            },
            {
                "count": 72,
                "userAgentName": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"
            },
            {
                "count": 72,
                "userAgentName": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
            },
            {
                "count": 72,
                "userAgentName": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 Kinza/4.7.2"
            },
            {
                "count": 72,
                "userAgentName": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.79 Safari/537.36"
            }
        ],
        "trafficOverTime": []
    }
}

Human Readable Output

[<IP object at 0x7f31335e0e80>]

Configuration parameters

  • url — Server URL (e.g. https://example.net) (required)
  • apikey — API Key (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • dbotBadThreshold — DBot Scoring - Bad Score Threshold (required)
  • dbotSuspiciousThreshold — DBot Scoring - Suspicious Score Threshold (required)
  • dbotGoodThreshold — DBot Scoring - Good Score Threshold (required)
  • integrationReliability — Source Reliability
  • feedExpirationPolicy
  • feedExpirationInterval

Commands (1)

  • ip

    Gets the PerimeterX DBotScore decision for a particular IP.

from CommonServerPython import *  # noqa: E402 lgtm [py/polluting-import]


# CONSTANTS
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"


class pXScoring:
    """
    Class to handle all current and future scoring for PerimeterX objects
    """

    @staticmethod
    def dbotscore_from_risk(risk_score: int, thresholds: dict[str, Any]) -> int:
        """
        Create the DBotScore structure first using the Common.DBotScore class.

        :type risk_score: ``int``
        :param risk_score: PerimeterX provided risk score for the IP Address

        :return: Returns the relevant DBotScore value for the provided PerimeterX risk score
        :rtype: ``int``
        """

        if risk_score > thresholds["bad_threshold"]:
            dbot_score = Common.DBotScore.BAD  # bad

        elif risk_score > thresholds["suspicious_threshold"]:
            dbot_score = Common.DBotScore.SUSPICIOUS  # suspicious

        elif risk_score > thresholds["good_threshold"]:
            dbot_score = Common.DBotScore.GOOD  # good

        else:
            dbot_score = Common.DBotScore.NONE  # unknown

        return dbot_score

    @staticmethod
    def get_ip_score(ip_address: str, risk_score: int, thresholds: dict[str, Any]):
        """
        Create the DBotScore structure first using the Common.DBotScore class.

        :type ip_address: ``str``
        :param ip_address: IP Address to be used as the indicator for this entry

        :type risk_score: ``int``
        :param risk_score: PerimeterX provided risk sco1re for the IP Address

        :return: Updated List of Common IP Addresses with this entry appended
        :rtype: ``List[Common.IP]``
        """

        # create the DBotScore object and populate it with the needed values
        dbot_score = Common.DBotScore(
            indicator=ip_address,
            indicator_type=DBotScoreType.IP,
            integration_name="PerimeterX",
            score=pXScoring.dbotscore_from_risk(risk_score, thresholds),
            malicious_description="High risk score indicates high probability that the requests from the IP are malicious ",
            reliability=demisto.params().get("integrationReliability"),
        )

        # Create the IP Standard Context structure using Common.IP and add
        # dbot_score to it.
        return Common.IP(
            ip=ip_address,
            # asn=ip_data.get('asn'),
            dbot_score=dbot_score,
        )


class Client(BaseClient):
    """
    Client will implement the service API, and should not contain any Demisto logic.
    Should only do requests and return data.
    """

    def test_api_connection(self, api_key: str) -> dict[str, Any]:
        """
        Makes a call to the status API path to confirm the proper URL and Authorization token were provided
        """

        headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

        return self._http_request(method="GET", full_url=f"{self._base_url}?search=ip:1.1.1.1&tops=path", headers=headers)

    def post_investigate_by_ip(self, ip_type: str, ip_address: str, api_key: str) -> dict[str, Any]:
        """
        Query the PerimeterX API to get the relevant details regarding the provided IP within a particular customer's own data

        :type ip_address: ``str``
        :param ip_address: IP Address to be used as the indicator for this entry

        :type ip_type: ``str``
        :param ip_type: The type of IP address we will be querying (true_ip or socket_ip)

        :return: The JSON response body from the PerimeterX API
        :rtype: ``Dict[str, Any]``
        """
        headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

        request_params: dict[str, Any] = {}

        if ip_type:
            request_params["ip_type"] = ip_type

        if ip_address:
            request_params["ip_address"] = f"search=ip:{ip_address}"

        return self._http_request(
            method="GET",
            full_url=f'{self._base_url}?{request_params["ip_address"]}&tops=user-agent,path,socket_ip_classification',
            headers=headers,
        )

    def post_investigate_by_name(self, name_type: str, name: str) -> dict[str, Any]:
        """
        THIS IS NOT CURRENTLY IMPLEMENTED
        Query the PerimeterX API to get the relevant details regarding the provided name within a particular customer's own data

        :type name: ``str``
        :param name: name to be used as the indicator for this entry

        :type name_type: ``str``
        :param name_type: The type of name we will be querying (domain or param)

        :return: The JSON response body from the PerimeterX API
        :rtype: ``Dict[str, Any]``
        """

        request_params: dict[str, Any] = {}

        if name_type:
            request_params["name_type"] = name_type

        if name:
            request_params["name"] = name

        return self._http_request(method="POST", full_url=self._base_url, json_data=request_params)


def test_module(client: Client, api_key):
    """
    Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful.

    Args:
        client: PerimeterX client

    Returns:
        'ok' if test passed, anything else will fail the test.
    """
    try:
        result = client.test_api_connection(api_key)
        if result["success"]:
            return "ok"
        else:
            return "Connection to api failed: " + result["errors"]
    except DemistoException as de:
        return "Connection to api failed with exception: " + de.message


def perimeterx_get_investigate_details(
    client: Client, args: dict[str, Any], thresholds: dict[str, Any], api_key: str
) -> CommandResults:
    """
    Collect the required details to query the PerimeterX API to get the relevant details regarding the provided
    search term within a particular customer's own data

    :type search_term: ``str``
    :param search_term: This is the entry that we'll be querying for against the PerimeterX API

    :type search_type: ``str``
    :param search_type: The type of query that will be run against the PerimeterX API

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains Domains

    :rtype: ``CommandResults``
    """

    # Check for the field to query for
    search_type = args.get("search_type", None)
    if not search_type:
        raise ValueError("No search_type specified")

    # Check to make sure we have a query term
    search_term = args.get("search_term", None)
    if not search_term:
        raise ValueError("No search_term specified")

    # Check for an IP based investigation
    supported_ip_search_types = ["true_ip", "socket_ip"]

    if search_type in supported_ip_search_types:
        """
        Run an IP based search if the search type is one supported by the IP types
        """
        result = client.post_investigate_by_ip(ip_type=search_type, ip_address=search_term, api_key=api_key)

        indicator = pXScoring.get_ip_score(ip_address=search_term, risk_score=result["max_risk_score"], thresholds=thresholds)

        return CommandResults(outputs_prefix="PerimeterX", outputs_key_field="", outputs=result, indicator=indicator)

    elif search_type == "name":
        """
        THIS IS NOT CURRENTLY IMPLEMENTED
        Run a name based search if the search type is name
        """
        result = client.post_investigate_by_name(name_type=search_type, name=search_term)

    else:
        """
        Generate an error because the search type is not supported
        """
        raise ValueError("Invalid search_type provided")

    return CommandResults(outputs_prefix="PerimeterX", outputs_key_field="", outputs=result)


def ip(client: Client, args, thresholds: dict[str, Any], api_key):
    """
    Collect the details to run an IP Reputation query against the PerimeterX API

    :type ip: ``str``
    :param ip: Results will be provided for list of IPs

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains Domains

    :rtype: ``CommandResults``
    """

    # Check to make sure we have a query term
    ip_list = argToList(args.get("ip"))
    results = []
    for ip_address in ip_list:
        result = client.post_investigate_by_ip(ip_type="true_ip", ip_address=ip_address, api_key=api_key)

        indicator = pXScoring.get_ip_score(ip_address=ip_address, risk_score=result["max_risk_score"], thresholds=thresholds)
        readable_output = f"{indicator}"
        cr = CommandResults(
            readable_output=readable_output,
            outputs_prefix="PerimeterX",
            outputs_key_field="",
            outputs=result,
            indicator=indicator,
        )
        results.append(cr)

    return results


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """
    params = demisto.params()
    api_key = params.get("apikey")

    # get the service API url
    base_url = urljoin(params["url"], "/v1/bot-defender/")
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)

    # get the DBot Thresholds
    thresholds = {
        "good_threshold": int(params.get("dbotGoodThreshold")),
        "suspicious_threshold": int(params.get("dbotSuspiciousThreshold")),
        "bad_threshold": int(params.get("dbotBadThreshold")),
        "unknown_threshold": 0,
    }

    command = demisto.command()
    LOG(f"Command being called is {command}")
    try:
        headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
        client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy)

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module(client, api_key)
            demisto.results(result)

        elif command == "ip":
            return_results(ip(client, demisto.args(), thresholds=thresholds, api_key=api_key))

        elif command == "perimeterx_get_investigate_details":
            return_results(
                perimeterx_get_investigate_details(client=client, args=demisto.args(), thresholds=thresholds, api_key=api_key)
            )

    # Log exceptions
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}")


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