Cyren Inbox Security

Cyren Inbox Security is an innovative solution that safeguards Office 365 mailboxes in your organization against evasive phishing, business email compromise (BEC), and fraud. This integration imports incidents from Cyren Inbox Security into XSOAR, and includes a playbook for incident resolution.

Utilities · Cyren Inbox Security

Details

IDCyren Inbox Security
ProviderData443
CategoryUtilities
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Cyren Inbox Security is an innovative solution that safeguards Office 365 mailboxes in your organization against evasive phishing, business email compromise (BEC), and fraud. This integration imports incidents from Cyren Inbox Security into XSOAR, and includes a playbook for incident resolution.
This integration was integrated and tested with version 1.0 of Cyren Inbox Security

Configure Cyren Inbox Security in Cortex

Parameter Description Required
Server URL The endpoint provided by your Cyren Representative. (use “sample” to test) True
Client ID The client iD provided by your Cyren Representative. (use “sample” to test) True
Client Secret The client secret provided by your Cyren Representative. (use “sample” to test) True
First fetch time 1 day, 2 days, etc… False
Maximum number of incidents per fetch   False
Incident type   False
Fetch incidents   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.

cyren-resolve-and-remediate


resolve a case and remediate incidents

Base Command

cyren-resolve-and-remediate

Input

Argument Name Description Required
case_id case ID. Required
resolution resolution. Possible values are: phishing, malware, clean, other. Optional
resolution_reason the reason of the resolution. Possible values are: Identified phishing URL, Identified suspicious sender, Other, Scam, Spam. Optional
resolution_reason_text free text for resolution reason. Optional
actions remediation actions to perform. Possible values are: MOVE_TO_SPAM, MOVE_TO_DELETED, ADD_BANNER, SOFT_DELETE, MOVE_TO_INBOX, REMOVE_BANNER. Optional

Context Output

Path Type Description
Cyren.data.status string status of actions performed

Command Example

!cyren-resolve-and-remediate resolution=phishing resolution_reason="Identified suspicious sender" case_id="62877980-6ac7-4944-b3fa-62ddf628a0fe" resolution_reason_text="I think it is phishing" actions=ADD_BANNER,MOVE_TO_DELETED

Context Example

{
    "Cyren": {
        "data": {
            "status": "ok"
        }
    }
}

Human Readable Output

cyren-resolve-and-remediate results

status
ok

end of results

cyren-reset-sample-fetch


resets integration to fetch a sample incident

Base Command

cyren-reset-sample-fetch

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

There is no context output for this command.

Command Example

!cyren-reset-sample-fetch

Human Readable Output

A sample incident will be created on the next execution of system fetch-incidents command

Configuration parameters

  • url — Server URL (required)
  • client_id — Client ID (required)
  • client_secret — Client Secret (required)
  • first_fetch — First fetch time
  • max_fetch — Maximum number of incidents per fetch
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • isFetch — Fetch incidents

Commands (1)

  • cyren-resolve-and-remediate

    resolve a case and remediate incidents.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

# noqa: F401
# noqa: F401
# noqa: F401
# noqa: F401
# noqa: F401
# noqa: F401
"""
CyrenInboxSecurity Integration for Cortex XSOAR (formerly Demisto)

"""

import json

import urllib3

# Disable insecure warnings
urllib3.disable_warnings()


""" CONSTANTS """


MAX_INCIDENTS_TO_FETCH = 50

""" CLIENT CLASS """


class Client(BaseClient):
    """Client class to interact with the service API

    This Client implements API calls, and does not contain any XSOAR logic.
    It handles requests and returns data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    """

    def get_token(self, client_id, client_secret):
        request_params = {"client_id": client_id, "client_secret": client_secret, "grant_type": "client_credentials"}

        return self._http_request(method="POST", url_suffix="/v1/token", params=request_params)

    def get_incidents(self, token, date_from, max_fetch):
        request_params = {"date_from": date_from, "limit": max_fetch}

        headers = {"Authorization": f"Bearer {token}"}

        return self._http_request(method="GET", url_suffix="/v1/incidents", headers=headers, params=request_params)

    def resolve_and_remediate(self, token, case_id, **kwargs):
        headers = {"Authorization": f"Bearer {token}"}

        return self._http_request(method="PATCH", url_suffix=f"/v1/cases/{case_id}/resolution", headers=headers, json_data=kwargs)


""" HELPER FUNCTIONS """


def convert_to_demisto_severity(severity):
    """Maps CyrenInboxSecurity severity to Cortex XSOAR severity

    Converts the CyrenInboxSecurity confidence levels (1, 2,
    3) to Cortex XSOAR incident severity (1 to 4)
    for mapping. Note, there are only 3 Cyren confidence levels

    :type severity: ``int``
    :param severity: severity as returned from the CyrenInboxSecurity API (int)

    :return: Cortex XSOAR Severity (2 to 4)
    :rtype: ``int``
    """

    return {0: IncidentSeverity.LOW, 1: IncidentSeverity.MEDIUM, 2: IncidentSeverity.HIGH, 3: IncidentSeverity.CRITICAL}[severity]


""" COMMAND FUNCTIONS """


def test_module(client, client_id, client_secret):
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works
     like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    :type client: ``Client``
    :param Client: CyrenInboxSecurity client to use

    :type client_id: ``str``
    :param client_id: A unique string that identifies the client

    :type client_secret: ``str``
    :param client_secret: A unique string that represent the client secret

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """

    try:
        client.get_token(client_id, client_secret)
    except DemistoException as e:
        raise e

    return "ok"


def simulate_fetch():
    """
    Ingesting a sample incident to XSOAR
    """

    # Ensure only one sample is created
    last_run = demisto.getLastRun()
    if last_run.get("sample_fetched", False):
        return []
    else:
        demisto.setLastRun({"sample_fetched": True})

    now_time = datetime.now()
    now_time_timestamp_seconds = int(date_to_timestamp(now_time) / 1000)
    sample_incident = {
        "incident_id": "incidentID-" + str(now_time_timestamp_seconds),
        "case_id": "caseID-123",
        "tx_id": "txID-" + str(now_time_timestamp_seconds),
        "threat_type": "phishing",
        "application": "office365",
        "connection_id": "connectionID",
        "connection_name": "ConnectionName",
        "org_id": 1,
        "tenant_id": "tenantID",
        "remediation_data": {
            "remediation_type": "auto_remediation",
            "remediation": "not_remediated",
            "policy_action": [
                {"performed_date": 1625484377, "messages": None, "action_type": "ADD_BANNER", "action_status": "success"},
                {"performed_date": 1625484378, "messages": None, "action_type": "MOVE_TO_SPAM", "action_status": "success"},
                {"performed_date": 1625484378, "messages": None, "action_type": "INCIDENT", "action_status": "success"},
            ],
            "policy_condition": [
                {"condition_type": "ORIGIN", "condition_value": "ALL"},
                {"condition_type": "EMAIL_STATE", "condition_value": "ALL"},
                {"condition_type": "REPORTED_BY", "condition_value": "ALL"},
            ],
            "policy_name": "Default Rule",
            "resolution": "pending",
        },
        "message_details": {
            "internet_message_id": "internetMessageID-" + str(now_time_timestamp_seconds),
            "targeted_employee": {"name": "user1", "address": "user1@sample.com"},
            "other_recipients": [],
            "sender": {"name": "user2", "address": "user2@sample.com"},
            "from": {"name": "user2", "address": "user2@sample.com"},
            "to_recipients": [{"name": "user1", "address": "user1@sample.com"}],
            "to_cc_recipients": [],
            "to_bcc_recipients": [],
            "reply_to": [],
            "email_subject": "subject - this is a sample",
            "email_body": "",
            "received_datetime": "2021-07-05T11:23:27Z",
            "sent_datetime": "2021-07-05T11:23:22Z",
            "created_datetime": "2021-07-05T11:23:26Z",
            "last_modified_datetime": "2021-07-05T11:23:28Z",
            "is_read": False,
            "is_external_from": False,
            "is_external_reply_to": False,
            "is_external_sender": False,
            "is_user_subscribed": True,
            "urls_classification": [
                {
                    "url": "http://phishing.com",
                    "classification_type": "malicious",
                    "is_phishing": True,
                    "classification": "malicious",
                }
            ],
            "user_id": "userID",
            "message_id": "messageID-" + str(now_time_timestamp_seconds),
            "attachments": [
                {
                    "file_size": 158,
                    "file_name": "attachment.html",
                    "file_hash": "hash",
                    "file_category": "PHISHING",
                    "aws_key": "key.zip",
                    "aws_bucket": "bucket",
                }
            ],
            "folder": "inbox",
        },
        "threat_indicators": [
            {
                "type": "url",
                "subType": "malicious_url",
                "value": "http://phishing.com",
                "details": {"categories": [""], "family_names": ["Generic"], "industries": [""], "screenshot": "screenshot.png"},
            },
            {
                "type": "attachment",
                "subType": "attachment_hash",
                "value": "hash",
                "details": {"categories": None, "family_names": None, "industries": None, "screenshot": ""},
                "attachment": {
                    "file_size": 158,
                    "file_name": "attachment.html",
                    "file_hash": "hash",
                    "file_category": "PHISHING",
                    "aws_key": "key.zip",
                    "aws_bucket": "screenshot",
                },
            },
        ],
        "feedback": [
            {
                "details": "phishing url",
                "tx_id": "txID",
                "feedback_type": "phishing",
                "created_at": 1625653814,
                "threat_indicators_type": ["url"],
            },
        ],
        "reported_by": "admin@sample.com",
        "confidence": 3,
        "created_at": now_time_timestamp_seconds,
        "updated_at": now_time_timestamp_seconds,
    }

    incidents = []
    incident_name = "Cyren Inbox Security Sample - {} ({})".format(
        sample_incident.get("threat_type", "phishing"), sample_incident.get("reported_by", "System")
    )
    incident_timestamp = now_time_timestamp_seconds
    incident_str_time = timestamp_to_datestring(incident_timestamp * 1000)
    incident = {
        "name": incident_name,
        "occurred": incident_str_time,
        "rawJSON": json.dumps(sample_incident),
        "severity": convert_to_demisto_severity(1),
    }

    incidents.append(incident)

    return incidents


def fetch_incidents(client, client_id, client_secret, last_run, first_fetch_time, max_fetch):
    """
    This function will execute each interval (default is 1 minute).

    Args:
        client (Client): the client object
        client_id (String): client id
        client_secret (String): client secret
        last_run (dict): a dict with last_fetch as
         a key and a timestamp in seconds as a value
        first_fetch_time (datetime): datetime object
         on when to start fetching incidents
        max_fetch (int): Maximum numbers of incidents per fetch
    Returns:
        incidents: Incidents that will be created in Cortex XSOAR
    """
    # last run is a dict with last_fetch as a key and
    #  a timestamp in seconds as a value
    last_fetch = last_run.get("last_fetch")

    # Handle first fetch time
    if last_fetch is None:
        # if missing, use what provided via first_fetch_time
        date_from = int(date_to_timestamp(first_fetch_time) / 1000)
    else:
        # otherwise use the stored last fetch
        date_from = last_fetch

    last_fetched_time = date_from

    incidents = []
    token = client.get_token(client_id, client_secret).get("data").get("access_token")
    cyren_incidents = client.get_incidents(token, date_from, max_fetch)
    for ci in cyren_incidents.get("data", []):
        incident_name = "Cyren Inbox Security - {} ({})".format(
            ci.get("threat_type", "phishing"), ci.get("reported_by", "System")
        )
        incident_timestamp = ci.get("created_at")
        incident_str_time = timestamp_to_datestring(incident_timestamp * 1000)
        incident = {
            "name": incident_name,
            "occurred": incident_str_time,
            "rawJSON": json.dumps(ci),
            "severity": convert_to_demisto_severity(ci.get("confidence", 1)),
        }

        incidents.append(incident)

        # Update last_fetch if the incident is newer than last_fetch
        if incident_timestamp > last_fetched_time:
            last_fetched_time = incident_timestamp + 1

    demisto.setLastRun({"last_fetch": last_fetched_time})

    return incidents


def resolve_and_remediate_command(client, args, client_id, client_secret):
    """
    Resolve a case and remediate it
    """
    case_id = args.get("case_id")
    resolution = args.get("resolution", "phishing")
    resolution_reason = args.get("resolution_reason", "")
    resolution_reason_text = args.get("resolution_reason_text", "")
    actions = args.get("actions", [])
    if actions:
        actions = actions.split(",")

    if client_id == client_secret == "sample":
        resp = {"data": {"status": "ok"}}
    else:
        token = client.get_token(client_id, client_secret).get("data").get("access_token")
        resp = client.resolve_and_remediate(
            token,
            case_id,
            resolution=resolution,
            resolution_reason=resolution_reason,
            resolution_reason_text=resolution_reason_text,
            actions=actions,
        )

    readable_output = tableToMarkdown("cyren-resolve-and-remediate results", resp["data"]) + "\n*** end of results ***"

    return CommandResults(outputs_prefix="Cyren", outputs=resp, readable_output=readable_output)


""" MAIN FUNCTION """


def main() -> None:
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """

    url = demisto.params().get("url")
    client_id = demisto.params().get("client_id")
    client_secret = demisto.params().get("client_secret")
    first_fetch_time = arg_to_datetime(demisto.params().get("first_fetch", "3 days").strip())
    max_results = arg_to_number(arg=demisto.params().get("max_fetch", 50))

    if max_results is None:
        max_results = 1

    if max_results > MAX_INCIDENTS_TO_FETCH:
        max_results = MAX_INCIDENTS_TO_FETCH

    try:
        client = Client(base_url=url, verify=True, proxy=False)

        if demisto.command() == "test-module":
            if url == client_id == client_secret == "sample":
                return_results("ok")
            else:
                result = test_module(client, client_id, client_secret)
                return_results(result)

        if demisto.command() == "fetch-incidents":
            if url == client_id == client_secret == "sample":
                incidents = simulate_fetch()
                demisto.incidents(incidents)
            else:
                incidents = fetch_incidents(
                    client=client,
                    client_id=client_id,
                    client_secret=client_secret,
                    last_run=demisto.getLastRun(),
                    first_fetch_time=first_fetch_time,
                    max_fetch=max_results,
                )
                demisto.incidents(incidents)

        if demisto.command() == "cyren-resolve-and-remediate":
            return_results(
                resolve_and_remediate_command(client, demisto.args(), client_id=client_id, client_secret=client_secret)
            )

    # Log exceptions and return errors
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """

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