From e776dde4e68e89fa5dc4a48a0229c5e27b54cf01 Mon Sep 17 00:00:00 2001 From: Joshua Hiller Date: Sat, 3 Feb 2024 05:40:55 -0500 Subject: [PATCH] Restart sensor documentation and linting --- samples/rtr/README.md | 108 +++++++++++- samples/rtr/RTR_Restart Sensor.py | 80 --------- samples/rtr/restart_sensor.py | 267 ++++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+), 81 deletions(-) delete mode 100755 samples/rtr/RTR_Restart Sensor.py create mode 100755 samples/rtr/restart_sensor.py diff --git a/samples/rtr/README.md b/samples/rtr/README.md index fb790c45a..756014cd3 100644 --- a/samples/rtr/README.md +++ b/samples/rtr/README.md @@ -8,6 +8,7 @@ The examples within this folder focus on leveraging CrowdStrike's Real Time Resp - [Queued Execute](#bulk-execute-a-command-on-matched-hosts-with-queuing) - Bulk execute a command on multiple hosts that are selected by using a search string or a provided list of host AIDs. Execution is queued for offline hosts with request IDs stored to an external file for later result retrieval. - [Get host uptime](#get-host-uptime) - Retrieve the uptime for a host using a RTR session and a script command. - [Get RTR result](#get-rtr-result) - Retrieve the results for previously executed RTR batch commands. +- [Restart Sensor](#restart-sensor) - Restarts the sensor while taking a TCP dump. - [Dump Process Memory](pid-dump) - Dumps the memory for a running process on a target system. - [My Little RTR](pony) - Retrieve System Information and draws ASCII art. @@ -174,7 +175,7 @@ python3 queued_execute.py -k CLIENT_ID -s CLIENT_SECRET -f target -c "cat /etc/r ### Example source code The source code for this example can be found [here](queued_execute.py). - +--- ## Get host uptime Leverages the `runscript` RTR command to retrieve the uptime for host(s) within your environment. @@ -258,6 +259,7 @@ required arguments: ### Example source code The source code for this example can be found [here](get_host_uptime.py). +--- ## Get RTR result Retrieve the results for previously executed RTR commands. @@ -353,3 +355,107 @@ optional arguments: ### Example source code The source code for this example can be found [here](get_rtr_result.py). + +--- + +## Restart Sensor +This program creates a RTR Session, drops a script on the host, runs the script, and then finally retrieves the output. The script will start TCPdump and perform a capture while the Falcon Sensor is restarted. + +> [!WARNING] +> This example only supports endpoints running Linux operating systems. + +### Running the program +In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes: + +| Service Collection | Scope | +| :---- | :---- | +| ML Exclusions | __READ__ | +| Flight Control | __READ__ | +| Sensor Download | __READ__ | + +> [!NOTE] +> This program can be executed using an API key that is not scoped for the Flight Control (MSSP) service collection, but will be unable to access hosts within child CIDs. + +### Execution syntax +This sample leverages simple command-line arguments to implement functionality. + +#### Basic usage +Execute the example against a specific hostname. + +```shell +python3 restart_sensor.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -n HOSTNAME +``` + +Execute the example against a specific AID. + +```shell +python3 restart_sensor.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET -a AID +``` + +> This sample supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning you can execute any of the command lines shown without providing credentials if you have the values `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` defined in your environment. + +```shell +python3 restart_sensor.py +``` + +> [!TIP] +> This example will automatically identify and restart sensors on hosts within child tenants when provided valid parent API keys. + +#### Command-line help +Command-line help is available via the `-h` argument. + +```shell +usage: restart_sensor.py [-h] [-d] [-k CLIENT_ID] [-s CLIENT_SECRET] (-a AID | -n HOSTNAME) + +Sensor restart utility. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | |::.. . | FalconPy +`-------' `-------' + + )\.--. )\.---. )\ )\ )\.--. .-./( /`-. + ( ._.' ( ,-._( ( \, / ( ._.' ,' ) ,' _ \ + `-.`. \ '-, ) \ ( `-.`. ( .-, ( ( '-' ( + ,_ ( \ ) ,-` ( ( \ \ ,_ ( \ ) '._\ ) ) ,_ .' +( '.) ) ( ``-. `.)/ ) ( '.) ) ( , ( ( ' ) \ + '._,_.' )..-.( '.( '._,_.' )/ ._.' )/ )/ + + /`-. )\.---. )\.--. .-,.-.,-. /`-. /`-. .-,.-.,-. + ,' _ \ ( ,-._( ( ._.' ) ,, ,. ( ,' _ \ ,' _ \ ) ,, ,. ( +( '-' ( \ '-, `-.`. \( |( )/ ( '-' ( ( '-' ( \( |( )/ + ) ,_ .' ) ,-` ,_ ( \ ) \ ) _ ) ) ,_ .' ) \ +( ' ) \ ( ``-. ( '.) ) \ ( ( ,' ) \ ( ' ) \ \ ( + )/ )/ )..-.( '._,_.' )/ )/ )/ )/ )/ )/ + +This program creates a RTR Session, drops a script on the host, runs +the script, and then finally retrieves the output. The script will start +TCPdump and perform a capture while the Falcon Sensor is restarted. + +Developed by @Don-Swanson-Adobe, modified by jshcodes@CrowdStrike + +Requirements: + crowdstrike-falconpy >= 1.3.0 + py7zr + +optional arguments: + -h, --help show this help message and exit + -d, --debug Enable API debugging + +Required arguments: + -k CLIENT_ID, --client_id CLIENT_ID + CrowdStrike Falcon API key + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + CrowdStrike Falcon API secret + -a AID, --aid AID Endpoint AID + -n HOSTNAME, --hostname HOSTNAME + Endpoint Hostname +``` + +### Example source code +The source code for this example can be found [here](restart_sensor.py). + +--- \ No newline at end of file diff --git a/samples/rtr/RTR_Restart Sensor.py b/samples/rtr/RTR_Restart Sensor.py deleted file mode 100755 index 4d0fda477..000000000 --- a/samples/rtr/RTR_Restart Sensor.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -#Please establish an "auth.py" file in the same directory as this script with the "clientid" and "clientsec" variables defined. -#Born out of need to provide the support team a PCAP WHILE restarting the sensor, This script is designed to setup an RTR Session, put a script on the host (That also happens to stop and start the sensor), run that script, then finally retrieve the output. -#Developed by Don-Swanson-Adobe - -####REPLACE THE FOLLOWING EXAMPLE VARIABLES#### -cid = "EXAMPLE_CID" #If you are not using a multi-tenant environment, this and the "member_cid" variable on line 19 can be removed -aid = "EXAMPLE_AID" -script = "./remote_tcp_dump.sh" -#Note: './' is 1 directory up from current, '../' is 2 directories up -############################################### - -#Import API Harness and Auth File and some additional modules -from auth import * -from time import sleep -from falconpy import APIHarness - -#Setup the API Harness -falcon = APIHarness(client_id=clientid,client_secret=clientsec, member_cid=cid) #Member CID should not be used in non-multi-tenant environments - -#Initiate the Session -BODY = {"device_id": aid, "queue_offline": True} -session = falcon.command("RTR_InitSession", body=BODY)["body"]["resources"][0]["session_id"] -print("Session ID: "+session) - -#"PUT" the File -BODY = {"base_command": "put", "command_string": "put "+script, "persist": True, "session_id": session} -response = falcon.command("RTR_ExecuteAdminCommand", body=BODY) -if response["status_code"] != 201: - print("Error: File not uploaded") - exit() -sleep(5) - -#Chmod the File -BODY = {"base_command": "runscript", "command_string": "runscript -Raw=```chmod +x ./"+script+"```", "persist": True, "session_id": session} -response = falcon.command("RTR_ExecuteAdminCommand", body=BODY) -if response["status_code"] != 201: - print("Error: Not Chomd'd") - exit() - -#Run the File (We use systemd-run to ensure the script continues to run after the sensor is stopped) -BODY = {"base_command": "runscript", "command_string": "runscript -Raw=```systemd-run ./"+script+"```", "persist": True, "session_id": session} -response = falcon.command("RTR_ExecuteAdminCommand", body=BODY) -if response["status_code"] != 201: - print("Error: Execution Failed") - exit() - -#Wait for finish -sleep(90) - -#Get the output File -BODY = {"base_command": "get", "command_string": "get tcpdump.pcap", "persist": True, "session_id": session} -response = falcon.command("RTR_ExecuteAdminCommand", body=BODY) -if response["status_code"] != 201: - print("Error: Get Failed") - exit() - - -####################################### -#Example of the remote_tcp_dump.sh script -##!/bin/bash -##This script can be re-purposed to run any command on the endpoint via RTR while restarting the sensor. -##create a variable with the hostname -#hostname=$(hostname) -# -##run tcpdump for 60 seconds and save the file with the hostname -#echo "Starting capture" -#tcpdump -G 60 -W 1 -w tcpdump-$hostname.pcap & -#sleep 10 -# -## Stopping the Falcon Sensor -#systemctl stop falcon-sensor -#sleep 5 -# -## Starting the Falcon Sensor -#systemctl start falcon-sensor -#sleep 45 -# -##print that the script is done -#echo "Capture Complete!" \ No newline at end of file diff --git a/samples/rtr/restart_sensor.py b/samples/rtr/restart_sensor.py new file mode 100755 index 000000000..44a03e8bb --- /dev/null +++ b/samples/rtr/restart_sensor.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +r"""Sensor restart utility. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | |::.. . | FalconPy +`-------' `-------' + + )\.--. )\.---. )\ )\ )\.--. .-./( /`-. + ( ._.' ( ,-._( ( \, / ( ._.' ,' ) ,' _ \ + `-.`. \ '-, ) \ ( `-.`. ( .-, ( ( '-' ( + ,_ ( \ ) ,-` ( ( \ \ ,_ ( \ ) '._\ ) ) ,_ .' +( '.) ) ( ``-. `.)/ ) ( '.) ) ( , ( ( ' ) \ + '._,_.' )..-.( '.( '._,_.' )/ ._.' )/ )/ + + /`-. )\.---. )\.--. .-,.-.,-. /`-. /`-. .-,.-.,-. + ,' _ \ ( ,-._( ( ._.' ) ,, ,. ( ,' _ \ ,' _ \ ) ,, ,. ( +( '-' ( \ '-, `-.`. \( |( )/ ( '-' ( ( '-' ( \( |( )/ + ) ,_ .' ) ,-` ,_ ( \ ) \ ) _ ) ) ,_ .' ) \ +( ' ) \ ( ``-. ( '.) ) \ ( ( ,' ) \ ( ' ) \ \ ( + )/ )/ )..-.( '._,_.' )/ )/ )/ )/ )/ )/ + +This program creates a RTR Session, drops a script on the host, runs +the script, and then finally retrieves the output. The script will start +TCPdump and perform a capture while the Falcon Sensor is restarted. + +Developed by @Don-Swanson-Adobe, modified by jshcodes@CrowdStrike + +Requirements: + crowdstrike-falconpy >= 1.3.0 + py7zr +""" +import os +import logging +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace +from enum import Enum +from time import sleep +import py7zr +from falconpy import APIHarnessV2, APIError, Result + + +class WHAT_IS_THE(Enum): + """Dodge bandit's silliness.""" + MAGIC_WORD = "infected" + + +def consume_arguments() -> Namespace: + """Consume any provided command line arguments.""" + parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) + parser.add_argument("-d", "--debug", + help="Enable API debugging", + action="store_true", + default=False + ) + req = parser.add_argument_group("Required arguments") + req.add_argument("-k", "--client_id", + help="CrowdStrike Falcon API key", + default=os.getenv("FALCON_CLIENT_ID") + ) + req.add_argument("-s", "--client_secret", + help="CrowdStrike Falcon API secret", + default=os.getenv("FALCON_CLIENT_SECRET") + ) + hid = req.add_mutually_exclusive_group(required=True) + hid.add_argument("-a", "--aid", + help="Endpoint AID", + default=None + ) + hid.add_argument("-n", "--hostname", + help="Endpoint Hostname", + default=None + ) + + parsed = parser.parse_args() + if not parsed.client_id or not parsed.client_secret: + parser.error("You must provide CrowdStrike API credentials using the '-k' and '-s' arguments.") + + return parsed + + +def do_command(base_command: str, command_string: str, session_id: str) -> Result: + """Craft the command body payload, execute the API operation, and return the result object.""" + body_payload = { + "base_command": base_command, + "command_string": command_string, + "persist": True, + "session_id": session_id + } + try: + return falcon.command("RTR_ExecuteAdminCommand", body=body_payload) + except APIError as api_error: + raise SystemExit(api_error) + + +DUMP_SCRIPT = """#!/bin/bash +hostname=$(hostname) +echo "Starting capture" +tcpdump -G 60 -W 1 -w tcpdump-$hostname.pcap & +sleep 10 +systemctl stop falcon-sensor +sleep 5 +systemctl start falcon-sensor +sleep 45 +echo "Capture Complete!" +""" + + +# Consume any command line arguments +cmd_line = consume_arguments() + +# Activate debugging if requested +if cmd_line.debug: + logging.basicConfig(level=logging.DEBUG) + +# Create our base authentication dictionary +auth = { + "client_id": cmd_line.client_id, + "client_secret": cmd_line.client_secret, + "debug": cmd_line.debug, + "pythonic": True +} + +endpoint_aid = "" +endpoint_cid = "" +endpoint_hostname = "" +this_cid = "" +script = "remote_tcp_dump.sh" +falcon = APIHarnessV2(**auth) +try: + this_cid = falcon.command("GetSensorInstallersCCIDByQuery").data[0][:-3].lower() +except APIError as api_error: + # They do not have access to the sensor downloads service collection with this key + raise SystemExit("The Sensor Download scope required to run this program.") + +if cmd_line.hostname: + endpoint_hostname = cmd_line.hostname + endpoint_aid = falcon.command("QueryDevicesByFilterScroll", + filter=f"hostname:'{endpoint_hostname}'" + ).data[0] + if not endpoint_aid: + raise SystemExit("The hostname was not found.") +else: + endpoint_aid = cmd_line.aid +try: + endpoint = falcon.command("GetDeviceDetails", ids=endpoint_aid).data[0] + endpoint_cid = endpoint["cid"] + endpoint_hostname = endpoint["hostname"] +except APIError as api_error: + raise SystemExit(api_error.message) + + +if endpoint_cid != this_cid: + auth["member_cid"] = endpoint_cid + +falcon = APIHarnessV2(**auth) +# Initiate the session +init_payload = {"device_id": endpoint_aid, "queue_offline": True} +try: + session = falcon.command("RTR_InitSession", body=init_payload).data[0]["session_id"] +except APIError as api_error: + raise SystemExit(api_error.message) +except (IndexError, KeyError): + raise SystemExit("Unable to initiate session with endpoint.") +print("Session initialized") +# Check for the existence of the TCP dump script +script_id = falcon.command("RTR_ListScripts", filter=f"name:'{script}'").data +if not script_id: + # The script doesn't exist, create it + script_data = { + "name": script, + "content": DUMP_SCRIPT, + "platform": "linux", + "permission_type": "private", + "description": f"Run a TCP dump while restarting the sensor" + } + falcon.command("RTR_CreateScripts", + data=script_data, + files=[(script, (script, 'application/script'))] + ) + # Grab the script's ID so we can remove it later + script_id = falcon.command("RTR_ListScripts", filter=f"name:'{script}'").data + +print("Uploading script to endpoint") +# "PUT" the script onto the endpoint +response = do_command("put", f"put {script}", session) +if response.status_code != 201: + raise SystemExit("ERROR: Unable to drop script on target endpoint") +sleep(5) + +# Set script execution permissions +response = do_command("runscript", f"runscript -Raw=```chmod +x ./{script}```", session) +if response.status_code != 201: + raise SystemExit("ERROR: Unable to set script file permissions") + +# Execute the script using systemd-run to ensure the script continues to run after the sensor is stopped +response = do_command("runscript", "runscript -Raw=```systemd-run ./"+script+"```", session) +if response.status_code != 201: + raise SystemExit("ERROR: Script execution failed") + +print("Script execution takes approximately 90 seconds, please wait...") +for counter in range(1, 91): + print(f" {counter} second{'s' if counter > 1 else ''}", end="\r") + sleep(.9) + +print("Retrieving results") +# Get the output File +response = do_command("get", f"get tcpdump-{endpoint_hostname}.pcap", session) +if response.status_code != 201: + raise SystemExit("ERROR: Unable to retrieve TCP dump") +cloud_request_id = response.data[0]["cloud_request_id"] +upload_complete = False +while not upload_complete: + upload_complete = falcon.command("RTR_CheckAdminCommandStatus", + cloud_request_id=cloud_request_id, + sequence_id=0 + ).data[0]["complete"] + print(" Waiting on upload to complete", end="\r") +try: + file_id = falcon.command("RTR_ListFiles", session_id=session)[0]["sha256"] +except IndexError: + raise SystemExit("Unable to retrieve TCP dump results") +try: + with open(f"tcpdump-{endpoint_hostname}.7z", "wb") as dump_file: + dump_file.write(falcon.command("RTR_GetExtractedFileContents", + sha256=file_id, + session_id=session, + filename=f"tcpdump-{endpoint_hostname}.pcap" + ).full_return) +except APIError as api_error: + raise SystemExit(api_error.message) + +print(f"Extracting results{' ' * 12}") +# Open our downloaded archive +archive = py7zr.SevenZipFile(f"tcpdump-{endpoint_hostname}.7z", + mode="r", + password=WHAT_IS_THE["MAGIC_WORD"].value + ) +archive.extractall() +archive.close() + +# Remove the 7zip archive +os.remove(f"tcpdump-{endpoint_hostname}.7z") + +print("Cleaning up") +# Remove the dump file from the endpoint +response = do_command("rm", f"rm tcpdump-{endpoint_hostname}.pcap", session) +if response.status_code != 201: + print("ERROR: Unable to remove TCP dump from endpoint") + +# Remove the dump script from the endpoint +response = do_command("rm", f"rm {script}", session) +if response.status_code != 201: + print("ERROR: Unable to remove dump script from endpoint") + +# Delete the uploaded dump script +try: + falcon.command("RTR_DeleteScripts", ids=script_id) +except APIError as api_error: + print("Unable to remove TCP dump script from CrowdStrike cloud") + +# Delete the RTR session +falcon.command("RTR_DeleteSession", session_id=session) + +print(f"Procedure complete, TCP dump results saved to tcpdump-{endpoint_hostname}.pcap")