RemoveFileWrapper

This script allows removing specified files using Cortex XDR, CrowdStrike and Microsoft Defender (Advanced Threat Protection).

python · Malware Core

Details

IDRemoveFileWrapper
Languagepython
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10404775
Tagsbasescript

README

This script is a wrapper for Cortex XDR and CrowdStrike to remove files in given path.

Script Data


Name Description
Script Type python3
Tags basescript
Cortex XSOAR Version 6.0.0

Inputs


Argument Name Description
device_ids List of device IDs.
file_path The file path of the file.

Outputs


Path Description Type
PaloAltoNetworksXDR.ScriptRun.action_id The ID of the initiated action. Number
PaloAltoNetworksXDR.ScriptRun.endpoints_count The number of endpoints the action was initiated on. Number
CrowdStrike.Command.rm.HostID The host ID. String
CrowdStrike.Command.rm.Error The error message raised if the command failed. String

Script Examples

Example command

!RemoveFileWrapper device_ids=0bde2c4645294245aca522971ccc44c4 file_path=/tmp/a.txt

Context Example

{
    "CrowdStrike": {
        "Command": {
            "rm": {
                "HostID": "0bde2c4645294245aca522971ccc44c4", 
                "Error": "Success"
            }
        }
    }
}

Human Readable Output

Results Summary

Instance Command Result Comment
CrowdstrikeFalcon: CrowdstrikeFalcon_instance_1 command: cs-falcon-rtr-remove-file
args:
host_ids: 0bde2c4645294245aca522971ccc44c4
file_path: /tmp/a.txt
os: Linux
Success  
Cortex XDR - IR: Cortex XDR - IR_instance_1 command: xdr-run-script-delete-file
args:
endpoint_ids: 0bde2c4645294245aca522971ccc44c4
file_path: /tmp/a.txt
Error Error in API call [00] - Internal Server Error
{"reply": {"err_code": 00, "err_msg": "An error occurred while processing XDR public API - No endpoint was found for creating the requested action", "err_extra": "can't create group action id for SCRIPT_EXECUTION"}}
Cortex XDR - IR: Cortex XDR - IR_instance_1_copy command: xdr-run-script-delete-file
args:
endpoint_ids: 0bde2c4645294245aca522971ccc44c4
file_path: /tmp/a.txt
Error Error in API call [00] - Internal Server Error
{"reply": {"err_code": 00, "err_msg": "An error occurred while processing XDR public API - No endpoint was found for creating the requested action", "err_extra": "can't create group action id for SCRIPT_EXECUTION"}}

CrowdStrike Falcon rm over the file: /tmp/a.txt

HostID Error
0bde2c4645294245aca522971ccc44c4 Success
import CommonServerPython
import pytest
import RemoveFileWrapper

test_data = [
    CommonServerPython.CommandRunner.Result(
        command="endpoint",
        args={"id": "device1,device2", "using-brand": "CrowdstrikeFalcon"},
        brand="CrowdstrikeFalcon",
        instance="CrowdstrikeFalcon_instance_1",
        result={
            "errors": [],
            "resources": [
                {"device_id": "device1", "platform_name": "Windows"},
                {"device_id": "device2", "platform_name": "Linux"},
            ],
        },
    )
]


def test_get_crowdstrike_os_to_id(mocker):
    """
    Given:
        A list of devices ids which are on crowdstrike
    When:
        Getting the os for each device
    Then:
        Return a mapping between an OS and a set of device-ids what are on the OS.

    """
    from RemoveFileWrapper import get_crowdstrike_os_to_id

    mocker.patch.object(RemoveFileWrapper.CommandRunner, "execute_commands", return_value=(test_data, []))
    os_to_id = get_crowdstrike_os_to_id(["device2", "device1"])
    assert os_to_id == {"Windows": {"device1"}, "Linux": {"device2"}}


def test_create_command_executors(mocker):
    """
    Given:
        the action to perform (allow or block)
    When:
        Calling `create_command_wrappers` to get all the command wrappers for the script.
    Then:
        Ensure the right commands wrappers are being returned.
    """
    from RemoveFileWrapper import create_commands, demisto

    device_ids = ["device1", "device2", "device3", "device4"]
    file_path = "filepath"
    file_hash = "filehash"
    mock_incident_id = 123
    mocker.patch.object(demisto, "incident", return_value={"id": mock_incident_id})
    mocker.patch.object(
        RemoveFileWrapper,
        "get_crowdstrike_os_to_id",
        return_value={"Windows": {"device1", "device2"}, "Linux": {"device3"}, "Mac": {"device4"}},
    )
    commands = create_commands(device_ids, file_path, file_hash)
    assert len(commands) == 3
    for command in commands:
        command_names = set(command.commands)
        if "xdr-run-script-delete-file" in command_names:
            assert command_names == {"xdr-run-script-delete-file"}
            assert command.args_lst == [{"endpoint_ids": ",".join(device_ids), "file_path": file_path}]
        if "cs-falcon-rtr-remove-file" in command_names:
            assert len(command.commands) == 3
            assert len(command.args_lst) == 3
            assert command_names == {"cs-falcon-rtr-remove-file"}
            assert set(command.args_lst[0].get("host_ids", "").split(",")) == {"device1", "device2"}
            assert command.args_lst[1:] == [
                {"host_ids": "device3", "file_path": file_path, "os": "Linux"},
                {
                    "host_ids": "device4",
                    "file_path": file_path,
                    "os": "Mac",
                },
            ]
        if "microsoft-ato-stop-and-quarantine-file" in command_names:
            assert command.commands == ["microsoft-ato-stop-and-quarantine-file"]
            assert command.args_lst == [
                {
                    "machine_id": "device1,device2,device3,device4",
                    "file_hash": "filehash",
                    "comment": f"Action was taken by Cortex XSOAR - incident #{mock_incident_id}",
                }
            ]


@pytest.mark.parametrize("approved", ("yes", "no", False))
def test_approve_action(mocker, approved: bool):
    """
    Given   a value for the `approve_action` argument
    When    calling main()
    Then    make sure an error is raised only when it should (when approved = 'no')
    """
    from RemoveFileWrapper import demisto, main

    mocker.patch.object(RemoveFileWrapper, "run_remove_file", return_value=None)
    mocker.patch.object(demisto, "error", return_value=None)
    return_error = mocker.patch.object(RemoveFileWrapper, "return_error", return_value=None)
    mocker.patch.object(
        demisto, "args", return_value={"approve_action": approved, "device_ids": ["device1"], "file_path": "file_path"}
    )
    main()
    assert return_error.call_count == int(approved != "yes")


@pytest.mark.parametrize("file_path", ("", "path"))
@pytest.mark.parametrize("file_hash", ("", "hash"))
def test_created_command_count(mocker, file_path: str, file_hash: str):
    """
    Given   the file_path and file_hash arguments
    When    calling create_commands
    Then    make sure the number of command created is correct
    """
    from RemoveFileWrapper import create_commands, demisto

    mocker.patch.object(RemoveFileWrapper, "run_remove_file", return_value=None)
    mocker.patch.object(demisto, "error", return_value=None)
    mocker.patch.object(
        demisto, "args", return_value={"approve_action": "yes", "device_ids": ["device1"], "file_path": "file_path"}
    )

    path_based_commands = {"xdr", "falcon"}
    hash_based_commands = {"microsoft_atp"}
    expected_commands = (path_based_commands if file_path else set()) | (hash_based_commands if file_hash else set())

    commands = create_commands(["id"], file_path, file_hash)
    assert len(commands) == len(expected_commands)