2019-04-19 14:06:35 +02:00
|
|
|
import base64
|
|
|
|
import io
|
2019-04-18 00:23:38 +02:00
|
|
|
import json
|
2019-04-19 14:06:35 +02:00
|
|
|
import logging
|
2019-04-18 00:23:38 +02:00
|
|
|
import requests
|
2019-04-19 14:06:35 +02:00
|
|
|
import sys
|
|
|
|
import urllib.parse
|
|
|
|
import zipfile
|
2019-04-18 00:23:38 +02:00
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
from requests.exceptions import RequestException
|
|
|
|
|
2019-04-19 16:24:30 +02:00
|
|
|
log = logging.getLogger("cuckoo_submit")
|
2019-04-19 14:06:35 +02:00
|
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
sh = logging.StreamHandler(sys.stdout)
|
|
|
|
sh.setLevel(logging.DEBUG)
|
|
|
|
fmt = logging.Formatter(
|
2019-04-19 16:24:30 +02:00
|
|
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
2019-04-19 14:06:35 +02:00
|
|
|
)
|
|
|
|
sh.setFormatter(fmt)
|
|
|
|
log.addHandler(sh)
|
|
|
|
|
|
|
|
moduleinfo = {
|
2019-04-19 16:24:30 +02:00
|
|
|
"version": "0.1", "author": "Evert Kors",
|
2019-04-19 14:06:35 +02:00
|
|
|
"description": "Submit files and URLs to Cuckoo Sandbox",
|
|
|
|
"module-type": ["expansion", "hover"]
|
|
|
|
}
|
|
|
|
misperrors = {"error": "Error"}
|
2019-04-19 16:24:30 +02:00
|
|
|
moduleconfig = ["api_url", "api_key"]
|
2019-04-18 00:23:38 +02:00
|
|
|
mispattributes = {
|
2019-04-19 16:24:30 +02:00
|
|
|
"input": ["attachment", "malware-sample", "url", "domain"],
|
2019-04-19 14:06:35 +02:00
|
|
|
"output": ["text"]
|
2019-04-18 00:23:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
class APIKeyError(RequestException):
|
|
|
|
"""Raised if the Cuckoo API returns a 401. This means no or an invalid
|
|
|
|
bearer token was supplied."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class CuckooAPI(object):
|
|
|
|
|
|
|
|
def __init__(self, api_url, api_key=""):
|
|
|
|
self.api_key = api_key
|
|
|
|
if not api_url.startswith("http"):
|
|
|
|
api_url = "https://{}".format(api_url)
|
|
|
|
|
|
|
|
self.api_url = api_url
|
|
|
|
|
|
|
|
def _post_api(self, endpoint, files=None, data={}):
|
|
|
|
data.update({
|
|
|
|
"owner": "MISP"
|
|
|
|
})
|
|
|
|
|
|
|
|
try:
|
|
|
|
response = requests.post(
|
|
|
|
urllib.parse.urljoin(self.api_url, endpoint),
|
|
|
|
files=files, data=data,
|
2019-04-19 16:24:30 +02:00
|
|
|
headers={"Authorization": "Bearer {}".format(self.api_key)}
|
2019-04-19 14:06:35 +02:00
|
|
|
)
|
|
|
|
except RequestException as e:
|
|
|
|
log.error("Failed to submit sample to Cuckoo Sandbox. %s", e)
|
|
|
|
return None
|
|
|
|
|
|
|
|
if response.status_code == 401:
|
|
|
|
raise APIKeyError("Invalid or no Cuckoo Sandbox API key provided")
|
|
|
|
|
2019-04-19 16:24:30 +02:00
|
|
|
if response.status_code != 200:
|
|
|
|
log.error("Invalid Cuckoo API response")
|
|
|
|
return None
|
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
return response.json()
|
|
|
|
|
|
|
|
def create_task(self, filename, fp):
|
|
|
|
response = self._post_api(
|
|
|
|
"/tasks/create/file", files={"file": (filename, fp)}
|
|
|
|
)
|
|
|
|
if not response:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return response["task_id"]
|
|
|
|
|
|
|
|
def create_url(self, url):
|
|
|
|
response = self._post_api(
|
|
|
|
"/tasks/create/url", data={"url": url}
|
|
|
|
)
|
|
|
|
if not response:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return response["task_id"]
|
2019-04-18 00:23:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
def handler(q=False):
|
|
|
|
if q is False:
|
|
|
|
return False
|
2019-04-19 14:06:35 +02:00
|
|
|
|
2019-04-18 00:23:38 +02:00
|
|
|
request = json.loads(q)
|
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
# See if the API URL was provided. The API key is optional, as it can
|
|
|
|
# be disabled in the Cuckoo API settings.
|
|
|
|
api_url = request["config"].get("api_url")
|
|
|
|
api_key = request["config"].get("api_key", "")
|
|
|
|
if not api_url:
|
|
|
|
misperrors["error"] = "No Cuckoo API URL provided"
|
2019-04-18 00:23:38 +02:00
|
|
|
return misperrors
|
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
url = request.get("url") or request.get("domain")
|
|
|
|
data = request.get("data")
|
|
|
|
filename = None
|
|
|
|
if data:
|
|
|
|
data = base64.b64decode(data)
|
2019-04-18 00:23:38 +02:00
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
if "malware-sample" in request:
|
|
|
|
filename = request.get("malware-sample").split("|", 1)[0]
|
|
|
|
with zipfile.ZipFile(io.BytesIO(data)) as zipf:
|
|
|
|
data = zipf.read(zipf.namelist()[0], pwd=b"infected")
|
2019-04-18 00:23:38 +02:00
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
elif "attachment" in request:
|
|
|
|
filename = request.get("attachment")
|
2019-04-18 00:23:38 +02:00
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
cuckoo_api = CuckooAPI(api_url=api_url, api_key=api_key)
|
|
|
|
task_id = None
|
2019-04-18 00:23:38 +02:00
|
|
|
try:
|
2019-04-19 14:06:35 +02:00
|
|
|
if url:
|
|
|
|
log.debug("Submitting URL to Cuckoo Sandbox %s", api_url)
|
|
|
|
task_id = cuckoo_api.create_url(url)
|
|
|
|
elif data and filename:
|
|
|
|
log.debug("Submitting file to Cuckoo Sandbox %s", api_url)
|
|
|
|
task_id = cuckoo_api.create_task(
|
|
|
|
filename=filename, fp=io.BytesIO(data)
|
|
|
|
)
|
|
|
|
except APIKeyError as e:
|
|
|
|
misperrors["error"] = "Failed to submit to Cuckoo: {}".format(e)
|
|
|
|
return misperrors
|
|
|
|
|
|
|
|
if not task_id:
|
|
|
|
misperrors["error"] = "File or URL submission failed"
|
2019-04-18 00:23:38 +02:00
|
|
|
return misperrors
|
|
|
|
|
2019-04-19 14:06:35 +02:00
|
|
|
return {
|
|
|
|
"results": [
|
|
|
|
{"types": "text", "values": "Cuckoo task id: {}".format(task_id)}
|
|
|
|
]
|
|
|
|
}
|
2019-04-18 00:23:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
def introspection():
|
|
|
|
return mispattributes
|
|
|
|
|
|
|
|
|
|
|
|
def version():
|
2019-04-19 16:24:30 +02:00
|
|
|
moduleinfo["config"] = moduleconfig
|
2019-04-18 00:23:38 +02:00
|
|
|
return moduleinfo
|