-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Release of the Cisco AMP for Endpoints Responder
- Loading branch information
Michael Auger
committed
Jan 24, 2020
1 parent
3cf76c2
commit dee7530
Showing
7 changed files
with
455 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
#!/usr/bin/env python | ||
# encoding: utf-8 | ||
|
||
import re | ||
import json | ||
import requests | ||
from cortexutils.responder import Responder | ||
|
||
|
||
class AMPforEndpoints(Responder): | ||
def __init__(self): | ||
Responder.__init__(self) | ||
self.service = self.get_param("config.service", None, "Service Missing") | ||
self.amp_cloud = self.get_param("config.amp_cloud", None, "AMP HOST Missing") | ||
self.client_id = self.get_param("config.client_id", None, "Client ID Missing") | ||
self.api_key = self.get_param("config.api_key", None, "API Key Missing") | ||
|
||
if self.service in ("scdadd", "scdremove"): | ||
self.scd_guid = self.get_param( | ||
"config.scd_guid", None, "Simple Custom Detectoin GUID Missing" | ||
) | ||
if self.service in ("moveguid"): | ||
self.group_guid = self.get_param( | ||
"config.group_guid", None, "Group GUID Missing" | ||
) | ||
if self.service in ("isolationstart"): | ||
self.unlock_code = self.get_param("config.unlock_code", None) | ||
|
||
self.amp_session = requests.Session() | ||
self.amp_session.auth = (self.client_id, self.api_key) | ||
self.amp_session.headers.update( | ||
{ | ||
"User-Agent": "AMPforEndpoints-Cortex-Responder", | ||
"Content-Type": "application/json", | ||
"Accept": "application/json", | ||
"Accept-Encoding": "gzip, deflate", | ||
} | ||
) | ||
|
||
def run(self): | ||
def parse_amp_error(error_response): | ||
"""Parse AMP for Endponts error response | ||
Return the human readable error message | ||
""" | ||
try: | ||
response = error_response.json() | ||
errors = response.get("errors", []) | ||
details = errors[0].get("details", []) | ||
error = details[0] | ||
return error | ||
except IndexError: | ||
return "Something went wrong! Recieved status code: {}".format( | ||
error_response.status_code | ||
) | ||
|
||
def validate_guid(guid): | ||
"""Validate the provided GUIDs is the correct format | ||
""" | ||
expression = r"^[A-Fa-f0-9]{8}\-[A-Fa-f0-9]{4}\-[A-Fa-f0-9]{4}\-[A-Fa-f0-9]{4}\-[A-Fa-f0-9]{12}$" | ||
return bool(re.match(expression, guid)) | ||
|
||
def scd_add(amp_cloud, scd_guid, sha256, caseId, title): | ||
"""Add a SHA256 to a Simple Custom Detection List | ||
""" | ||
url = "https://{}/v1/file_lists/{}/files/{}".format( | ||
amp_cloud, scd_guid, sha256 | ||
) | ||
|
||
body = { | ||
"description": "The Hive Case ID: {} Case Title: {}".format( | ||
caseId, title | ||
) | ||
} | ||
body = json.dumps(body) | ||
response = self.amp_session.post(url, data=body) | ||
|
||
if response.status_code == 201: | ||
self.report({"message": "SHA256 Added to SCD"}) | ||
if response.status_code == 409: | ||
self.error("SHA256 already on SCD list") | ||
else: | ||
self.error("Failed to add to blacklist.") | ||
|
||
def scd_remove(amp_cloud, scd_guid, sha256): | ||
"""Remove a SHA256 from a Simple Custom Detection List | ||
""" | ||
url = "https://{}/v1/file_lists/{}/files/{}".format( | ||
amp_cloud, scd_guid, sha256 | ||
) | ||
|
||
response = self.amp_session.delete(url) | ||
|
||
if response.status_code == 200: | ||
self.report({"message": "SHA256 removed from SCD"}) | ||
else: | ||
error = parse_amp_error(response) | ||
self.error(error) | ||
|
||
def move_guid(amp_cloud, group_guid, connector_guid): | ||
"""Move a connector GUID to a new group | ||
""" | ||
url = "https://{}/v1/computers/{}".format(amp_cloud, connector_guid) | ||
|
||
body = {"group_guid": group_guid} | ||
body = json.dumps(body) | ||
|
||
response = self.amp_session.patch(url, data=body) | ||
response_json = response.json() | ||
data = response_json.get("data", {}) | ||
hostname = data.get("hostname", "for uknown hostname") | ||
|
||
if response.status_code == 202: | ||
self.report({"message": "Connector {} moved".format(hostname)}) | ||
else: | ||
error = parse_amp_error(response) | ||
self.error(error) | ||
|
||
def isolation_start(amp_cloud, connector_guid, unlock_code): | ||
"""Send request to start host isolation for a connector | ||
""" | ||
url = "https://{}/v1/computers/{}/isolation".format( | ||
amp_cloud, connector_guid | ||
) | ||
|
||
if unlock_code: | ||
body = {"unlock_code": unlock_code} | ||
body = json.dumps(body) | ||
response = self.amp_session.put(url, data=body) | ||
else: | ||
response = self.amp_session.put(url) | ||
|
||
if response.status_code == 200: | ||
self.report( | ||
{ | ||
"message": "Request sent to start isolation for connector: {}".format( | ||
connector_guid | ||
) | ||
} | ||
) | ||
else: | ||
error = parse_amp_error(response) | ||
self.error(error) | ||
|
||
def isolation_stop(amp_cloud, connector_guid): | ||
"""Send request to stop host isolation for a connector | ||
""" | ||
url = "https://{}/v1/computers/{}/isolation".format( | ||
amp_cloud, connector_guid | ||
) | ||
|
||
response = self.amp_session.delete(url) | ||
|
||
if response.status_code == 200: | ||
self.report( | ||
{ | ||
"message": "Request sent to stop isolation for connector: {}".format( | ||
connector_guid | ||
) | ||
} | ||
) | ||
else: | ||
error = parse_amp_error(response) | ||
self.error(error) | ||
|
||
Responder.run(self) | ||
|
||
dataType = self.get_param("data.dataType") | ||
|
||
if dataType == "hash" and self.service in ("scdadd", "scdremove"): | ||
sha256 = self.get_param("data.data", None) | ||
caseId = self.get_param("data.case.caseId", None, "caseId is missing") | ||
title = self.get_param("data.case.title", None, "title is missing").encode( | ||
"utf-8" | ||
) | ||
|
||
# Valide a valid SHA256 was provided | ||
if not re.match(r"^[A-Fa-f0-9]{64}$", sha256): | ||
self.error("{} is not a SHA256".format(sha256)) | ||
|
||
# Add SHA256 to Simple Custom Detection list | ||
if self.service == "scdadd": | ||
scd_add(self.amp_cloud, self.scd_guid, sha256, caseId, title) | ||
|
||
# Remove SHA256 from Simple Custom Detection list | ||
if self.service == "scdremove": | ||
scd_remove(self.amp_cloud, self.scd_guid, sha256) | ||
|
||
if dataType == "other" and self.service in ( | ||
"moveguid", | ||
"isolationstart", | ||
"isolationstop", | ||
): | ||
connector_guid = self.get_param("data.data", None) | ||
|
||
# Validate the connector GUID is the right format | ||
if not validate_guid(connector_guid): | ||
self.error( | ||
"{} is not a valid AMP connector GUID".format(connector_guid) | ||
) | ||
|
||
# Validate the Group GUID is the right format | ||
if self.service in ("moveguid") and not validate_guid(self.group_guid): | ||
self.error("{} is not a valid AMP Group GUID".format(self.group_guid)) | ||
|
||
# Move the connector GUID to a new group | ||
if self.service in ("moveguid"): | ||
move_guid(self.amp_cloud, self.group_guid, connector_guid) | ||
|
||
# Start host isolation | ||
if self.service in ("isolationstart"): | ||
# Check if the unlock_code is less than 24 characters | ||
if len(self.unlock_code) > 24: | ||
self.error( | ||
"Validation failed: Unlock Code is invalid, Unlock Code is too long. (Maximum 24 characters)" | ||
) | ||
# Check if the unlock_code contains spaces | ||
if bool(" " in self.unlock_code): | ||
self.error( | ||
"Validation failed: Unlock Code is invalid, Unlock Code cannot contain spaces" | ||
) | ||
isolation_start(self.amp_cloud, connector_guid, self.unlock_code) | ||
|
||
# Stop host isolation | ||
if self.service in ("isolationstop"): | ||
isolation_stop(self.amp_cloud, connector_guid) | ||
|
||
# Return an error for all other datatypes | ||
self.error( | ||
"Incorrect dataType received '{}' as '{}'".format( | ||
self.get_param("data.data", None), dataType | ||
) | ||
) | ||
|
||
def operations(self, raw): | ||
if self.service == "scdadd": | ||
return [self.build_operation("AddTagToArtifact", tag="AMP:blocked")] | ||
|
||
|
||
if __name__ == "__main__": | ||
AMPforEndpoints().run() |
44 changes: 44 additions & 0 deletions
44
responders/AMPforEndpoints/AMPforEndpoints_IsolationStart.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "AMPforEndpoints_IsolationStart", | ||
"version": "1.0", | ||
"author": "Cisco Security", | ||
"url": "https://github.com/CiscoSecurity", | ||
"license": "MIT", | ||
"description": "Start host isolation for an AMP for Endpoints connector", | ||
"dataTypeList": ["thehive:case_artifact"], | ||
"command": "AMPforEndpoints/AMPforEndpoints.py", | ||
"baseConfig": "AMPforEndpoints", | ||
"config": { | ||
"service": "isolationstart" | ||
}, | ||
"configurationItems": [ | ||
{ | ||
"name": "amp_cloud", | ||
"description": "FQDN of the AMP for Endpoints cloud to interact with", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "client_id", | ||
"description": "Client ID for AMP for Endpoints", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "api_key", | ||
"description": "API Key for AMP for Endpoints", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "unlock_code", | ||
"description": "Custom unlock code used to stop isolation from the endpoint (Maximum 24 characters)", | ||
"type": "string", | ||
"multi": false, | ||
"required": false | ||
} | ||
] | ||
} |
37 changes: 37 additions & 0 deletions
37
responders/AMPforEndpoints/AMPforEndpoints_IsolationStop.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "AMPforEndpoints_IsolationStop", | ||
"version": "1.0", | ||
"author": "Cisco Security", | ||
"url": "https://github.com/CiscoSecurity", | ||
"license": "MIT", | ||
"description": "Stop host isolation for an AMP for Endpoints connector", | ||
"dataTypeList": ["thehive:case_artifact"], | ||
"command": "AMPforEndpoints/AMPforEndpoints.py", | ||
"baseConfig": "AMPforEndpoints", | ||
"config": { | ||
"service": "isolationstop" | ||
}, | ||
"configurationItems": [ | ||
{ | ||
"name": "amp_cloud", | ||
"description": "FQDN of the AMP for Endpoints cloud to interact with", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "client_id", | ||
"description": "Client ID for AMP for Endpoints", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "api_key", | ||
"description": "API Key for AMP for Endpoints", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "AMPforEndpoints_MoveGUID", | ||
"version": "1.0", | ||
"author": "Cisco Security", | ||
"url": "https://github.com/CiscoSecurity", | ||
"license": "MIT", | ||
"description": "Move an AMP for Endpoints connector GUID to a different Group", | ||
"dataTypeList": ["thehive:case_artifact"], | ||
"command": "AMPforEndpoints/AMPforEndpoints.py", | ||
"baseConfig": "AMPforEndpoints", | ||
"config": { | ||
"service": "moveguid" | ||
}, | ||
"configurationItems": [ | ||
{ | ||
"name": "amp_cloud", | ||
"description": "FQDN of the AMP for Endpoints cloud to interact with", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "client_id", | ||
"description": "Client ID for AMP for Endpoints", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "api_key", | ||
"description": "API Key for AMP for Endpoints", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "group_guid", | ||
"description": "AMP for Endpoints Group GUID for the group connectors will be moved to", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
} | ||
] | ||
} |
Oops, something went wrong.