Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ProxyTool sample and documentation #1058

Merged
merged 9 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions samples/proxytool/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike)

# ProxyTool v3.5
# ProxyTool v3.6
This example focuses on leveraging CrowdStrike's Hosts, Host Groups, Sensor Download, and Real-Time Response API.

It is a script that fetches CID or Host Group hosts, and uses the batch command and offline queuing of Real-Time Response API to centrally
Expand All @@ -11,7 +11,9 @@ and conveniently issue Falcon sensor proxy configuration changes.
- Because it uses the RTR API it is run centrally through our cloud, it does NOT need to be distributed to each targeted host.
- The script uses the queuing feature of RTR, so hosts don't need to be online at the time the script is executed, they will receive the commands if they connect to our cloud within the next 7 days.
- The script checks that the CID provided as a scope_id argument is the same the API client is working with.

- Handles API authentication errors gracefully.
- It can be used in both Windows and Linux sensors.

‼️WARNING‼️
This script has the potential to disrupt communications between the Falcon sensor and the cloud. It is recommended users test with a limited Host Group first to troubleshoot any issues.

Expand All @@ -22,8 +24,15 @@ In order to run this demonstration, you will need access to CrowdStrike API keys
| Hosts | __READ__ |
| Host Group | __READ__ |
| Real-Time Response | __WRITE, READ__ |
| Real-Time Response (admin) | __WRITE__ |
| Sensor Download | __READ__ |

In addition to this you will need the endpoints to be assigned to a "[Response Policy](https://falcon.crowdstrike.com/configuration/real-time-response/policies)" that allows "Real Time Response".

‼️WARNING‼️
The Linux response policy will need to have the "custom scripts" setting enabled.


### Execution syntax
This example accepts the following input parameters.
| Parameter | Purpose | Category |
Expand Down
158 changes: 128 additions & 30 deletions samples/proxytool/proxytool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@

CHANGE LOG

07/08/2023 v3.6 Add Linux support and handle API authentication errors
16/08/2023 v3.5 Add sanity check when using CID as scope
28/02/2023 v3.4 Add ability to disable/delete proxy config
27/10/2022 v3.3 Use command line arguments instead of external file for config
26/10/2022 v3.2 Add support for Host Group or CID selection
25/10/2022 v3.1 Ported to falconpy SDK instead of reinventing the wheel
23/10/2022 v3.0 Rewrote 2.0 for error handling, logging and fetching host IDs from API
"""


# Import dependencies
import datetime
from argparse import ArgumentParser, RawTextHelpFormatter

version = "3.6"

# Define logging function
def log(msg):
"""Print the log message to the terminal."""
Expand All @@ -34,6 +39,7 @@ def log(msg):
Hosts,
OAuth2,
RealTimeResponse,
RealTimeResponseAdmin,
HostGroup,
SensorDownload
)
Expand Down Expand Up @@ -102,36 +108,19 @@ def log(msg):



# Main routine
def main(): # pylint: disable=R0912,R0915,C0116
log("Starting execution of ProxyTool")

log("Authenticating to API")
auth = OAuth2(client_id=args.falcon_client_id,
client_secret=args.falcon_client_secret,
base_url=args.base_url
)
# Fetch list of hosts, platform_name is Windows or Linux
def fetch_hosts(platform_name):
global auth

# Check which CID the API client is operating in, as sanity check. Exit if operating CID does not match provided scope_id.
# Fixes https://github.com/CrowdStrike/falconpy/issues/1018
falcon = SensorDownload(auth_object=auth, base_url=args.base_url)
current_cid = falcon.get_sensor_installer_ccid()["body"]["resources"][0][:-3]
if (args.scope.lower() == "cid" and (args.scope_id.lower() != current_cid.lower())):
log(f"The entered CID [{args.scope_id.upper()}] does not match the API client CID [{current_cid.upper()}].")
raise SystemExit(f"The entered CID [{args.scope_id.upper()}] does not match the API client CID [{current_cid.upper()}].")


# Fetch list of hosts
if args.scope.lower() == "cid":
log(f"Getting all hosts from CID [{args.scope_id}]")
log(f"Getting all {platform_name} hosts from CID [{args.scope_id}]")
falcon = Hosts(auth_object=auth, base_url=args.base_url)
else:
log(f"Getting all hosts from host group ID [{args.scope_id}]")
log(f"Getting all {platform_name} hosts from host group ID [{args.scope_id}]")
falcon = HostGroup(auth_object=auth, base_url=args.base_url)


offset = ""
hosts_all = []
offset = ""

while True:
batch_size = 5000 # 5000 is max supported by API
Expand All @@ -140,20 +129,20 @@ def main(): # pylint: disable=R0912,R0915,C0116
# Fetch all Windows CID hosts
response = falcon.query_devices_by_filter_scroll(offset=offset,
limit=batch_size,
filter="platform_name:'Windows'"
filter=f"platform_name:'{platform_name}'"
)

else:
# Fetch all Windows host group ID hosts
if offset == "":
response = falcon.query_group_members(limit=batch_size,
filter="platform_name:'Windows'",
filter=f"platform_name:'{platform_name}'",
id=args.scope_id
)
else:
response = falcon.query_group_members(offset=offset,
limit=batch_size,
filter="platform_name:'Windows'",
filter=f"platform_name:'{platform_name}'",
id=args.scope_id
)

Expand All @@ -169,26 +158,87 @@ def main(): # pylint: disable=R0912,R0915,C0116
if len(hosts_all) >= int(response['body']['meta']['pagination']['total']):
break

log(f"-- Retrieved a total of {str(len(hosts_all))} hosts")
log(f"-- Retrieved a total of {str(len(hosts_all))} {platform_name} hosts")
return(hosts_all)



# Now that we have the host IDs, we create a batch RTR list of commands to execute it in all hosts

def rtr_linux(hosts_all):
# Now that we have the host IDs, we create a batch RTR list of commands to execute it in all hosts
falcon = RealTimeResponse(auth_object=auth, base_url=args.base_url)
falcon_admin = RealTimeResponseAdmin(auth_object=auth, base_url=args.base_url)

# Get batch id
response = falcon.batch_init_sessions(host_ids=hosts_all, queue_offline=True)

batch_id = response['body']['batch_id']


if batch_id:
log(f"Initiated RTR batch with id {batch_id} for LINUX hosts")
else:
raise SystemExit("Unable to initiate RTR session with hosts.")

if not args.proxy_disable:
# Enable proxy
# Configure proxy: sudo /opt/CrowdStrike/falconctl -s --aph=<proxy host> --app=<proxy port>
command = f"/opt/CrowdStrike/falconctl -s --aph={args.proxy_hostname} --app={args.proxy_port}"
response = falcon_admin.batch_admin_command(batch_id=batch_id,
base_command="runscript",
command_string=f"runscript -Raw=```{command}```"
)
if response["status_code"] == 201:
log(f"-- Command: {command}")
else:
raise SystemExit(f"Error, Response: {response['status_code']} - {response.text}")

# Enable proxy: sudo /opt/CrowdStrike/falconctl -s --apd=FALSE
command = f"/opt/CrowdStrike/falconctl -s --apd=FALSE"
response = falcon_admin.batch_admin_command(batch_id=batch_id,
base_command="runscript",
command_string=f"runscript -Raw=```{command}```"
)
if response["status_code"] == 201:
log(f"-- Command: {command}")
else:
raise SystemExit(f"Error, Response: {response['status_code']} - {response.text}")

else:
# Disable proxy
# Disable proxy: sudo /opt/CrowdStrike/falconctl -s --apd=TRUE
command = f"/opt/CrowdStrike/falconctl -s --apd=TRUE"
response = falcon_admin.batch_admin_command(batch_id=batch_id,
base_command="runscript",
command_string=f"runscript -Raw=```{command}```"
)
if response["status_code"] == 201:
log(f"-- Command: {command}")
else:
raise SystemExit(f"Error, Response: {response['status_code']} - {response.text}")


log("-- Finished launching RTR commands, please check progress in the RTR audit logs")





def rtr_windows(hosts_all):
# Now that we have the host IDs, we create a batch RTR list of commands to execute it in all hosts
falcon = RealTimeResponse(auth_object=auth, base_url=args.base_url)

# Get batch id
response = falcon.batch_init_sessions(host_ids=hosts_all, queue_offline=True)
batch_id = response['body']['batch_id']

if batch_id:
log(f"Initiated RTR batch with id {batch_id}")
log(f"Initiated RTR batch with id {batch_id} for WINDOWS hosts")
else:
raise SystemExit("Unable to initiate RTR session with hosts.")


# Commands to change proxy config

registry_stores = [
r"HKLM:\SYSTEM\Crowdstrike\{9b03c1d9-3138-44ed-9fae-d9f4c034b88d}\{16e0423f-7058-48c9-a204-725362b67639}\Default",
r"HKLM:\SYSTEM\CurrentControlSet\Services\CSAgent\Sim"
Expand Down Expand Up @@ -256,6 +306,54 @@ def main(): # pylint: disable=R0912,R0915,C0116
raise SystemExit(f"Error, Response: {response['status_code']} - {response.text}")

log("-- Finished launching RTR commands, please check progress in the RTR audit logs")






# Main routine
def main(): # pylint: disable=R0912,R0915,C0116

global auth

log(f"Starting execution of ProxyTool (version {version})")

log("Authenticating to API")
auth = OAuth2(client_id=args.falcon_client_id,
client_secret=args.falcon_client_secret,
base_url=args.base_url
)

# Check which CID the API client is operating in, as sanity check. Exit if operating CID does not match provided scope_id.
falcon = SensorDownload(auth_object=auth, base_url=args.base_url)
current_cid = falcon.get_sensor_installer_ccid()["body"]["resources"][0][:-3]

if falcon.token_status < 204:
log(f"Authentication correct.")
else:
log(f"Authentication error: {falcon.token_status} - {falcon.token_fail_reason}")
raise SystemExit(f"Authentication error: {falcon.token_status} - {falcon.token_fail_reason}")


response = falcon.get_sensor_installer_ccid()
current_cid = response["body"]["resources"][0][:-3]

if (args.scope.lower() == "cid" and (args.scope_id.lower() != current_cid.lower())):
log(f"The entered CID [{args.scope_id.upper()}] does not match the API client CID [{current_cid.upper()}].")
raise SystemExit(f"The entered CID [{args.scope_id.upper()}] does not match the API client CID [{current_cid.upper()}].")


hosts_windows = fetch_hosts("Windows")
hosts_linux = fetch_hosts("Linux")

if len(hosts_windows) > 0:
rtr_windows(hosts_windows)

if len(hosts_linux) > 0:
rtr_linux(hosts_linux)


log("End")

if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def ioc_run_all_tests(self):
}
for key in tests:
if tests[key]["status_code"] not in AllowedResponses:
if not (tests[key]["status_code"] == 403 and key in Allowed403):
if not (tests[key]["status_code"] in [403, 500] and key in Allowed403):
error_checks = False
# print(tests[key])
# print(f"{key} operation returned a {tests[key]['status_code']} status code")
Expand Down
Loading