/analyzers/ThreatResponse/ThreatResponse.py

https://github.com/TheHive-Project/Cortex-Analyzers · Python · 228 lines · 160 code · 40 blank · 28 comment · 28 complexity · e2e8673f725a7dc7812f85672547d2eb MD5 · raw file

  1. #!/usr/bin/env python3
  2. # encoding: utf-8
  3. import re
  4. from copy import deepcopy
  5. from cortexutils.analyzer import Analyzer
  6. from threatresponse import ThreatResponse
  7. class ThreatResponseAnalyzer(Analyzer):
  8. """
  9. Cisco Threat Response analyzer
  10. """
  11. def __init__(self):
  12. Analyzer.__init__(self)
  13. self.region = self.get_param("config.region").lower()
  14. self.client_id = self.get_param(
  15. "config.client_id", None, "No Threat Response client ID given."
  16. )
  17. self.client_password = self.get_param(
  18. "config.client_password", None, "No Threat Response client Password given."
  19. )
  20. self.extract_amp_targets = self.get_param("config.extract_amp_targets", False)
  21. # Validate that the supplied region is valid
  22. if self.region and self.region not in ("us", "eu", "apjc"):
  23. self.error(
  24. "{} is not a valid Threat Response region. Must be 'us', 'eu', or 'apjc'".format(
  25. self.region
  26. )
  27. )
  28. # Set region to '' if 'us' was supplied
  29. if self.region == "us":
  30. self.region = ""
  31. # Create Threat Response client
  32. self.client = ThreatResponse(
  33. client_id=self.client_id,
  34. client_password=self.client_password,
  35. region=self.region,
  36. )
  37. def run(self):
  38. def identify_hash(observable):
  39. """Validate the provided hash is a supported type
  40. """
  41. # RegEx for supported checksum types MD5, SHA1, SHA256
  42. hash_mapping = {
  43. re.compile(r"^[A-Za-z0-9]{32}$"): "md5",
  44. re.compile(r"^[A-Za-z0-9]{40}$"): "sha1",
  45. re.compile(r"^[A-Za-z0-9]{64}$"): "sha256",
  46. }
  47. for expression in hash_mapping:
  48. if expression.match(observable):
  49. return hash_mapping[expression]
  50. def parse_verdicts(response_json):
  51. """Parse response from Threat Response and extract verdicts
  52. """
  53. verdicts = []
  54. for module in response_json.get("data", []):
  55. module_name = module["module"]
  56. for doc in module.get("data", {}).get("verdicts", {}).get("docs", []):
  57. verdicts.append(
  58. {
  59. "observable_value": doc["observable"]["value"],
  60. "observable_type": doc["observable"]["type"],
  61. "expiration": doc["valid_time"]["end_time"],
  62. "module": module_name,
  63. "disposition_name": doc["disposition_name"],
  64. }
  65. )
  66. return verdicts
  67. def parse_targets(response_json):
  68. """Parse response Threat Response and extract targets
  69. """
  70. result = []
  71. for module in response_json.get("data", []):
  72. module_name = module["module"]
  73. targets = []
  74. for doc in module.get("data", {}).get("sightings", {}).get("docs", []):
  75. for target in doc.get("targets", []):
  76. element = deepcopy(target)
  77. element.pop("observed_time", None)
  78. if element not in targets:
  79. targets.append(element)
  80. if targets:
  81. result.append(
  82. {
  83. "module": module_name,
  84. "targets": targets,
  85. }
  86. )
  87. return result
  88. # Map The Hive observable types to Threat Response observable types
  89. observable_mapping = {
  90. "domain": "domain",
  91. "mail": "email",
  92. "mail_subject": "email_subject",
  93. "filename": "file_name",
  94. "fqdn": "domain",
  95. "hash": None,
  96. "ip": "ip",
  97. "url": "url",
  98. }
  99. # Map the provided region to the FQDN
  100. host_mapping = {
  101. "": "visibility.amp.cisco.com",
  102. "us": "visibility.amp.cisco.com",
  103. "eu": "visibility.eu.amp.cisco.com",
  104. "apjc": "visibility.apjc.amp.cisco.com",
  105. }
  106. dataType = self.get_param("dataType")
  107. # Validate the supplied observable type is supported
  108. if dataType in observable_mapping.keys():
  109. observable = self.get_data() # Get the observable data
  110. # If the observable type is 'hash' determine which type of hash
  111. # Threat Response only supports MD5, SHA1, SHA256
  112. if dataType == "hash":
  113. hash_type = identify_hash(observable)
  114. if hash_type:
  115. observable_mapping["hash"] = hash_type
  116. else:
  117. self.error(
  118. "{} is not a valid MD5, SHA1, or SHA256".format(observable)
  119. )
  120. # Format the payload to be sent to the Threat Response API
  121. payload = [{"value": observable, "type": observable_mapping[dataType]}]
  122. # Query Threat Response Enrich API
  123. response = self.client.enrich.observe.observables(payload)
  124. # Parse verdicts from response for display
  125. verdicts = parse_verdicts(response)
  126. # Parse targets from response for display
  127. targets = parse_targets(response)
  128. # Build raw report
  129. raw_report = {
  130. "response": response,
  131. "targets": targets,
  132. "verdicts": verdicts,
  133. "host": host_mapping[self.region],
  134. "observable": observable,
  135. }
  136. self.report(raw_report)
  137. else:
  138. self.error("Data type {} not supported".format(dataType))
  139. def summary(self, raw):
  140. taxonomies = []
  141. namespace = "TR"
  142. verdicts = raw.get("verdicts", [])
  143. # Map Threat Response dispositions to The Hive levels
  144. level_mapping = {
  145. "Clean": "safe",
  146. "Common": "safe",
  147. "Malicious": "malicious",
  148. "Suspicious": "suspicious",
  149. "Unknown": "info",
  150. }
  151. for verdict in verdicts:
  152. disposition_name = verdict.get(
  153. "disposition_name"
  154. ) # Clean, Common, Malicious, Suspicious, Unknown
  155. module = verdict.get("module")
  156. taxonomies.append(
  157. self.build_taxonomy(
  158. level_mapping[disposition_name], namespace, module, disposition_name
  159. )
  160. )
  161. # Inform if not module returned a verdict
  162. if len(verdicts) < 1:
  163. taxonomies.append(
  164. self.build_taxonomy("info", namespace, "Enrich", "No Verdicts")
  165. )
  166. # level, namespace, predicate, value
  167. return {"taxonomies": taxonomies}
  168. def artifacts(self, raw):
  169. artifacts = []
  170. if self.extract_amp_targets:
  171. for module in raw.get("targets", []):
  172. for target in module.get("targets", []):
  173. for observable in target.get("observables", []):
  174. if observable.get("type") == "hostname":
  175. hostname = observable.get("value")
  176. if observable.get("type") == "amp_computer_guid":
  177. guid = observable.get("value")
  178. if guid:
  179. tags = []
  180. if hostname:
  181. tags.append("AMP Hostname:{}".format(hostname))
  182. tags.append("AMP GUID")
  183. artifacts.append(
  184. self.build_artifact("other", guid, tags=tags)
  185. )
  186. return artifacts
  187. if __name__ == "__main__":
  188. ThreatResponseAnalyzer().run()