CBLiveGetFile_V2

This automation translates an endpoints hostname/IP to the Carbon Black sensor ID. It then opens a session to the endpoint to download the given file paths and closes the session.

python · Carbon Black Enterprise Response

Source

import json
from contextlib import contextmanager

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

"""Globals"""
ERROR_SENSOR = -1
ERROR_SESSION = -1


""" STANDALONE FUNCTION """


def search_sensor_id(endpoint: str) -> int:
    """Retrieve list of connected sensors from:
            Integration: VMware Carbon Black EDR (Live Response API).
            Command: cb-list-sensors.
    Args:
        endpoint: Endpoint name - hostname/IP

    Returns:
        str: sensor id if found else empty string.
    """
    sensor_id = ERROR_SENSOR
    # Execute command and extract sensors
    output = demisto.executeCommand("cb-list-sensors", {})
    sensors = dict_safe_get(
        output,
        [0, "EntryContext", "CbResponse.Sensors(val.CbSensorID==obj.CbSensorID)"],
        default_return_value=[],
        return_type=list,
    )  # type: ignore
    # Search for sensor with endpoint or ip
    for sensor in sensors:
        is_same_ipaddress = endpoint in dict_safe_get(
            sensor, ["IPAddress", "IPAddresses"], default_return_value=[], return_type=list
        )
        is_same_endpoint = sensor.get("Hostname") == endpoint
        if is_same_endpoint or is_same_ipaddress:
            sensor_id = sensor.get("CbSensorID", ERROR_SENSOR)
            break

    return sensor_id


def search_active_session(sensor_id: int) -> int:
    """Search if exists current active session to sensor (It exists will use this session).

    Args:
        sensor_id: Sensor id to search session for.

    Returns:
        str: Exists active session to sensor, If not exists return '0'.
    """
    output = demisto.executeCommand("cb-list-sessions", {"sensor": sensor_id, "status": "active"})
    session_id = dict_safe_get(
        output,
        [0, "EntryContext", "CbLiveResponse.Sessions(val.CbSessionID==obj.CbSessionID)", 0, "CbSessionID"],
        ERROR_SESSION,
        int,
    )

    return session_id


def create_active_session(sensor_id: int, timeout: str) -> int:
    """Create active session to sensor.

    Args:
        sensor_id: Sensor to create new session for.
        timeout: Session timeout.

    Returns:
        str: New active session to sensor, If not able to create session return '0'.
    """
    session_id = ERROR_SESSION

    for trial in range(3):
        try:
            output = demisto.executeCommand("cb-session-create-and-wait", {"sensor": sensor_id, "command-timeout": timeout})
            raw_response = json.loads(dict_safe_get(output, [0, "Contents"]))
            session_id = dict_safe_get(raw_response, ["id"], ERROR_SESSION)
            break
        except json.JSONDecodeError:
            # Session could be failing due to Carbon Response bug, We retry to get session 3 times, Before failing.
            if trial == 2:
                raise Exception("Unable to parse entry context while creating session, try to raise timeout argument.")

    return session_id


def close_session(session_id):
    """Close sensor session.

    Args:
        session_id: Session id to be closed
    """
    demisto.executeCommand("cb-session-close", {"session": session_id})


@contextmanager
def open_session(endpoint: str, timeout: str):
    """Handler to Carbon Black sessions.

    Enter:
        1. Translate endpoint name to sensor id.
        2. Search for current active session to sensor id.
        3. If not exists -> Create new active session.

    Args:
        endpoint: Endpoint name to be handled.
        timeout: Session timeout.

    Yields:
        int: active session id.

    Raises:
        Exception: If session not succefully established.
    """
    active_session = ERROR_SESSION
    try:
        # Get sensor id from endpoint name (IP/Hostname)
        sensor_id = search_sensor_id(endpoint)
        if sensor_id == ERROR_SENSOR:
            raise Exception(f"Sensor with {endpoint} is not connected!")
        # Get session to communicate with sensor.
        active_session = search_active_session(sensor_id)
        if active_session == ERROR_SESSION:
            active_session = create_active_session(sensor_id, timeout)
        # Validate that session established succesfully
        if active_session == ERROR_SESSION:
            raise Exception(f"Unable to establish active session to {endpoint}, sensor: {sensor_id}")
        # Yield active session for communication.
        yield active_session

    except Exception as e:
        raise Exception(f"Unable to establish session to endpoint {endpoint}.\nError:{e}")
    finally:
        close_session(active_session)


def get_file_from_endpoint_path(session_id: str, path: str) -> tuple[dict | list, dict]:
    """Get file from file from session (endpoint/sensor).

    Args:
        session_id: Actvie session id.
        path: Path of file to be retrieved.

    Returns:
        dict/list: entry context.
        dict: raw response.

    Raises:
        Exception: If file can't be retrieved.
    """
    try:
        # Get file from enpoint
        output = demisto.executeCommand("cb-get-file-from-endpoint", {"session": session_id, "path": path})
        entry_context = dict_safe_get(output, [0, "EntryContext"])
        # Output file to war-room as soon as possible, But removing human-readable so it will be a single summary in the end.
        output[0]["HumanReadable"] = ""
        demisto.results(output)

    except Exception as e:
        raise Exception(f"Session established but file can't retrieved from endpoint.\nError:{e}")

    return entry_context


def cb_live_get_file(endpoint: str, path: str, timeout: str):
    """Download list of files from endpoint.

    Args:
        endpoint: Endpoint name to be handled.
        path: List of file paths to download from endpoint.
        timeout: Session timeout.

    Returns:
        list: collected entry contexts from command "cb-get-file-from-endpoint".
    """
    entry_contexts = []
    with open_session(endpoint, timeout) as active_session:
        for single_path in argToList(path):
            entry_context = get_file_from_endpoint_path(active_session, single_path)
            entry_contexts.append(entry_context)

    return entry_contexts


def build_table_dict(entry_contexts: List[dict]) -> List[dict]:
    """Create table from all retirieved entry context.

    Args:
        entry_contexts: List of entry contexts from command "cb-get-file-from-endpoint"

    Returns:
        list: filtered list with modified headers
    """
    table = []
    for ec in entry_contexts:
        table_entry = {}

        for file_ec in ec.values():
            for key, value in file_ec.items():
                if key == "FileID":
                    table_entry["File ID"] = value
                elif key == "OperandObject":
                    table_entry["File path"] = value

        table.append(table_entry)

    return table


""" COMMAND FUNCTION """


def cb_live_get_file_command(**kwargs) -> tuple[str, dict, dict]:
    entry_contexts = cb_live_get_file(**kwargs)
    human_readable = tableToMarkdown(
        name=f"Files downloaded from endpoint {kwargs.get('endpoint')}", t=build_table_dict(entry_contexts)
    )

    return human_readable, {}, {}


""" MAIN FUNCTION """


def main():
    try:
        return_outputs(*cb_live_get_file_command(**demisto.args()))
    except Exception as e:
        return_error(f"Failed to execute CBLiveGetFile_v2. Error: {e!s}")


""" ENTRY POINT """

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

README

This automation translate endpoint (hostname/IP) to sensor id, Than it downloads given file paths by opening session and closing it.

Script Data


Name Description
Script Type python3
Tags carbon-black, endpoint
Cortex XSOAR Version 4.1.0

Inputs


Argument Name Description
endpoint hostname of a Carbon Black sensor (Could be IP).
path Comma seprated files path to download from the endpoint..
timeout Session timeout (ms).

Outputs


Path Description Type
CbLiveResponse.Commands.CbCommandID Unique command identifier. Number
CbLiveResponse.Commands.CommandName The command name. String
CbLiveResponse.Commands.Status The command Status (‘pending’, ‘in progress’, ‘complete’, ‘error’, ‘canceled’). String
CbLiveResponse.Commands.CommandCompletionTime The command completion time (0 if not complete). String
CbLiveResponse.Commands.OperandObject The source path of the file. String
CbLiveResponse.Commands.FileID Unique file ID. Number
CbLiveResponse.File.Size File size. String
CbLiveResponse.File.SHA1 File SHA1. String
CbLiveResponse.File.SHA256 File SHA256. String
CbLiveResponse.File.Name File name. String
CbLiveResponse.File.SSDeep File SSDeep. String
CbLiveResponse.File.EntryID File EntryID. Number
CbLiveResponse.File.Info File info. String
CbLiveResponse.File.Type File type. String
CbLiveResponse.File.MD5 File MD5. String
CbLiveResponse.File.Extension File extension. String

Command Example

!CBLiveGetFile_v2 endpoint=EC2AMAZ-L4C2OKC path="c:\\Users\\All Users\\Desktop\\mooncake.jpg" timeout=2000

Context Example

{
    "CbLiveResponse": {
        "Commands": {
            "CbCommandID": 2,
            "CbSensorID": 17,
            "CbSessionID": 356,
            "CommandCompletionTime": 1540229207.655335,
            "CommandName": "get file",
            "CreateTime": 1540229207.608662,
            "FileID": 1,
            "OperandObject": "c:\\Users\\All Users\\Desktop\\mooncake.jpg",
            "Result": {
                "Code": 0,
                "Desc": "",
                "Type": "WinHresult"
            },
            "Status": "complete"
        }
    },
    "File": {
        "EntryID": "168@583490",
        "Extension": "jpg",
        "Info": "image/jpeg",
        "MD5": "1fe52b291d16c7f9a6eaf43074024011",
        "Name": "mooncake.jpg",
        "SHA1": "30bd2461d6cee80227bcf557a6fd47922b96263c",
        "SHA256": "a87b0fa1006b301b7ef2259cfa9aed2ff12c15217796b5dd08b36e006a137cd2",
        "SSDeep": "192:pAzQbZ/ujghzcZHcsWw6o6E7ODeADcBwjZ4P:pAzG/ujgh6xCo60ODe3wj8",
        "Size": 11293,
        "Type": "data\n"
    }
}

Human Readable Output

Files downloaded from endpoint EC2AMAZ-L4C2OKC

File ID File Path
1 “c:\Users\All Users\Desktop\mooncake.jpg”