Skip to content

Commit

Permalink
Cisco AMP for Endpoints Responder
Browse files Browse the repository at this point in the history
Initial Release of the Cisco AMP for Endpoints Responder
  • Loading branch information
Michael Auger committed Jan 24, 2020
1 parent 3cf76c2 commit dee7530
Show file tree
Hide file tree
Showing 7 changed files with 455 additions and 0 deletions.
240 changes: 240 additions & 0 deletions responders/AMPforEndpoints/AMPforEndpoints.py
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 responders/AMPforEndpoints/AMPforEndpoints_IsolationStart.json
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 responders/AMPforEndpoints/AMPforEndpoints_IsolationStop.json
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
}
]
}
44 changes: 44 additions & 0 deletions responders/AMPforEndpoints/AMPforEndpoints_MoveGUID.json
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
}
]
}
Loading

0 comments on commit dee7530

Please sign in to comment.