PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/cuckoo/reporting/misp.py

https://github.com/cuckoobox/cuckoo
Python | 210 lines | 178 code | 26 blank | 6 comment | 32 complexity | 8d478420892a4ab92f26323a0e0e656e MD5 | raw file
  1. # Copyright (C) 2016-2019 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # See the file 'docs/LICENSE' for copying permission.
  4. import logging
  5. import os.path
  6. import shlex
  7. import warnings
  8. from cuckoo.common.abstracts import Report
  9. from cuckoo.common.exceptions import CuckooProcessingError
  10. from cuckoo.common.safelist import (
  11. is_safelisted_mispdomain, is_safelisted_mispip, is_safelisted_mispurl,
  12. is_safelisted_misphash
  13. )
  14. log = logging.getLogger(__name__)
  15. class MISP(Report):
  16. """Enrich MISP with Cuckoo results."""
  17. def sample_hashes(self, results, event):
  18. if results.get("target", {}).get("file", {}):
  19. f = results["target"]["file"]
  20. self.misp.add_hashes(
  21. event,
  22. category="Payload delivery",
  23. filename=f["name"],
  24. md5=f["md5"],
  25. sha1=f["sha1"],
  26. sha256=f["sha256"],
  27. comment="File submitted to Cuckoo",
  28. )
  29. def all_urls(self, results, event):
  30. """All of the accessed URLS as per the PCAP."""
  31. urls = set()
  32. for protocol in ("http_ex", "https_ex"):
  33. for entry in results.get("network", {}).get(protocol, []):
  34. if is_safelisted_mispdomain(entry["host"]):
  35. continue
  36. if is_safelisted_mispdomain(entry["host"]):
  37. continue
  38. url = "%s://%s%s" % (
  39. entry["protocol"], entry["host"], entry["uri"])
  40. if not is_safelisted_mispurl(url):
  41. urls.add(url)
  42. self.misp.add_url(event, sorted(list(urls)))
  43. def domain_ipaddr(self, results, event):
  44. domains, ips = {}, set()
  45. for domain in results.get("network", {}).get("domains", []):
  46. if is_safelisted_mispip(domain["ip"]):
  47. continue
  48. if is_safelisted_mispdomain(domain["domain"]):
  49. continue
  50. domains[domain["domain"]] = domain["ip"]
  51. ips.add(domain["ip"])
  52. ipaddrs = set()
  53. for ipaddr in results.get("network", {}).get("hosts", []):
  54. if ipaddr not in ips and not is_safelisted_mispip(ipaddr):
  55. ipaddrs.add(ipaddr)
  56. self.misp.add_domains_ips(event, domains)
  57. self.misp.add_ipdst(event, sorted(list(ipaddrs)))
  58. def family(self, results, event):
  59. for config in results.get("metadata", {}).get("cfgextr", []):
  60. self.misp.add_detection_name(
  61. event, config["family"], "External analysis"
  62. )
  63. for cnc in config.get("cnc", []):
  64. self.misp.add_url(event, cnc)
  65. for url in config.get("url", []):
  66. self.misp.add_url(event, url)
  67. for mutex in config.get("mutex", []):
  68. self.misp.add_mutex(event, mutex)
  69. for user_agent in config.get("user_agent", []):
  70. self.misp.add_useragent(event, user_agent)
  71. def signature(self, results, event):
  72. for sig in results.get("signatures", []):
  73. marks = []
  74. if sig["ttp"]:
  75. marks.append("%s" % ", ".join(sig["ttp"]))
  76. for mark in sig.get("marks", []):
  77. if mark["type"] == "generic":
  78. marks.append(
  79. "%s %s" % (mark.get("parent_process", ""),
  80. mark.get("martian_process", ""))
  81. )
  82. marks.append(
  83. "%s %s" % (mark.get("reg_key", ""),
  84. mark.get("reg_value", ""))
  85. )
  86. marks.append(
  87. "%s %s" % (mark.get("option", ""),
  88. mark.get("value", ""))
  89. )
  90. marks.append("%s" % mark.get("domain", ""))
  91. marks.append("%s" % mark.get("description", ""))
  92. marks.append("%s" % mark.get("host", ""))
  93. elif mark["type"] == "call":
  94. if not mark["call"]["api"] in marks:
  95. marks.append(mark["call"]["api"])
  96. elif mark["type"] == "config":
  97. marks.append(mark["config"].get("url", ""))
  98. else:
  99. marks.append(mark[mark["type"]])
  100. markslist = ", ".join([x for x in marks if x and x != " "])
  101. data = "%s - (%s)" % (sig["description"], markslist)
  102. self.misp.add_internal_comment(event, data)
  103. for att, description in sig["ttp"].items():
  104. if not description:
  105. log.warning("Description for %s is not found", att)
  106. continue
  107. self.misp.add_internal_comment(
  108. event, "TTP: %s, short: %s" % (att, description["short"])
  109. )
  110. def run(self, results):
  111. """Submit results to MISP.
  112. @param results: Cuckoo results dict.
  113. """
  114. url = self.options.get("url")
  115. apikey = self.options.get("apikey")
  116. mode = shlex.split(self.options.get("mode") or "")
  117. score = results.get("info", {}).get("score", 0)
  118. upload_sample = self.options.get("upload_sample")
  119. if results.get("target", {}).get("category") == "file":
  120. f = results.get("target", {}).get("file", {})
  121. hash_safelisted = is_safelisted_misphash(f["md5"]) or \
  122. is_safelisted_misphash(f["sha1"]) or \
  123. is_safelisted_misphash(f["sha256"])
  124. if hash_safelisted:
  125. return
  126. if score < self.options.get("min_malscore", 0):
  127. return
  128. if not url or not apikey:
  129. raise CuckooProcessingError(
  130. "Please configure the URL and API key for your MISP "
  131. "instance."
  132. )
  133. with warnings.catch_warnings():
  134. warnings.simplefilter("ignore")
  135. import pymisp
  136. self.misp = pymisp.PyMISP(url, apikey, False, "json")
  137. # Get default settings for a new event
  138. distribution = self.options.get("distribution") or 0
  139. threat_level = self.options.get("threat_level") or 4
  140. analysis = self.options.get("analysis") or 0
  141. tag = self.options.get("tag") or "Cuckoo"
  142. event = self.misp.new_event(
  143. distribution=distribution,
  144. threat_level_id=threat_level,
  145. analysis=analysis,
  146. info="Cuckoo Sandbox analysis #%d" % self.task["id"]
  147. )
  148. # Add a specific tag to flag Cuckoo's event
  149. if tag:
  150. mispresult = self.misp.tag(event["Event"]["uuid"], tag)
  151. if mispresult.has_key("message"):
  152. log.debug("tag event: %s" % mispresult["message"])
  153. if upload_sample:
  154. target = results.get("target", {})
  155. if target.get("category") == "file" and target.get("file"):
  156. self.misp.upload_sample(
  157. filename=os.path.basename(self.task["target"]),
  158. filepath_or_bytes=self.task["target"],
  159. event_id=event["Event"]["id"],
  160. category="External analysis",
  161. )
  162. self.signature(results, event)
  163. if "hashes" in mode:
  164. self.sample_hashes(results, event)
  165. if "url" in mode:
  166. self.all_urls(results, event)
  167. if "ipaddr" in mode:
  168. self.domain_ipaddr(results, event)
  169. self.family(results, event)