mirror of https://github.com/MISP/misp-modules
170 lines
7.4 KiB
Python
170 lines
7.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
import json
|
|
from . import check_input_attribute, standard_error_message
|
|
from assemblyline_client import Client, ClientError
|
|
from collections import defaultdict
|
|
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
|
|
misperrors = {'error': 'Error'}
|
|
mispattributes = {'input': ['link'], 'format': 'misp_standard'}
|
|
|
|
moduleinfo = {'version': '1', 'author': 'Christian Studer',
|
|
'description': 'Query AssemblyLine with a report URL to get the parsed data.',
|
|
'module-type': ['expansion']}
|
|
moduleconfig = ["apiurl", "user_id", "apikey", "password", "verifyssl"]
|
|
|
|
|
|
class AssemblyLineParser():
|
|
def __init__(self):
|
|
self.misp_event = MISPEvent()
|
|
self.results = {}
|
|
self.attribute = {'to_ids': True}
|
|
self._results_mapping = {'NET_DOMAIN_NAME': 'domain', 'NET_FULL_URI': 'url',
|
|
'NET_IP': 'ip-dst'}
|
|
self._file_mapping = {'entropy': {'type': 'float', 'object_relation': 'entropy'},
|
|
'md5': {'type': 'md5', 'object_relation': 'md5'},
|
|
'mime': {'type': 'mime-type', 'object_relation': 'mimetype'},
|
|
'sha1': {'type': 'sha1', 'object_relation': 'sha1'},
|
|
'sha256': {'type': 'sha256', 'object_relation': 'sha256'},
|
|
'size': {'type': 'size-in-bytes', 'object_relation': 'size-in-bytes'},
|
|
'ssdeep': {'type': 'ssdeep', 'object_relation': 'ssdeep'}}
|
|
|
|
def get_submission(self, attribute, client):
|
|
sid = attribute['value'].split('=')[-1]
|
|
try:
|
|
if not client.submission.is_completed(sid):
|
|
self.results['error'] = 'Submission not completed, please try again later.'
|
|
return
|
|
except Exception as e:
|
|
self.results['error'] = f'Something went wrong while trying to check if the submission in AssemblyLine is completed: {e.__str__()}'
|
|
return
|
|
try:
|
|
submission = client.submission.full(sid)
|
|
except Exception as e:
|
|
self.results['error'] = f"Something went wrong while getting the submission from AssemblyLine: {e.__str__()}"
|
|
return
|
|
self._parse_report(submission)
|
|
|
|
def finalize_results(self):
|
|
if 'error' in self.results:
|
|
return self.results
|
|
event = json.loads(self.misp_event.to_json())
|
|
results = {key: event[key] for key in ('Attribute', 'Object', 'Tag') if (key in event and event[key])}
|
|
return {'results': results}
|
|
|
|
def _create_attribute(self, result, attribute_type):
|
|
attribute = MISPAttribute()
|
|
attribute.from_dict(type=attribute_type, value=result['value'], **self.attribute)
|
|
if result['classification'] != 'UNCLASSIFIED':
|
|
attribute.add_tag(result['classification'].lower())
|
|
self.misp_event.add_attribute(**attribute)
|
|
return {'referenced_uuid': attribute.uuid, 'relationship_type': '-'.join(result['context'].lower().split(' '))}
|
|
|
|
def _create_file_object(self, file_info):
|
|
file_object = MISPObject('file')
|
|
filename_attribute = {'type': 'filename'}
|
|
filename_attribute.update(self.attribute)
|
|
if file_info['classification'] != "UNCLASSIFIED":
|
|
tag = {'Tag': [{'name': file_info['classification'].lower()}]}
|
|
filename_attribute.update(tag)
|
|
for feature, attribute in self._file_mapping.items():
|
|
attribute.update(tag)
|
|
file_object.add_attribute(value=file_info[feature], **attribute)
|
|
return filename_attribute, file_object
|
|
for feature, attribute in self._file_mapping.items():
|
|
file_object.add_attribute(value=file_info[feature], **attribute)
|
|
return filename_attribute, file_object
|
|
|
|
@staticmethod
|
|
def _get_results(submission_results):
|
|
results = defaultdict(list)
|
|
for k, values in submission_results.items():
|
|
h = k.split('.')[0]
|
|
for t in values['result']['tags']:
|
|
if t['context'] is not None:
|
|
results[h].append(t)
|
|
return results
|
|
|
|
def _get_scores(self, file_tree):
|
|
scores = {}
|
|
for h, f in file_tree.items():
|
|
score = f['score']
|
|
if score > 0:
|
|
scores[h] = {'name': f['name'], 'score': score}
|
|
if f['children']:
|
|
scores.update(self._get_scores(f['children']))
|
|
return scores
|
|
|
|
def _parse_report(self, submission):
|
|
if submission['classification'] != 'UNCLASSIFIED':
|
|
self.misp_event.add_tag(submission['classification'].lower())
|
|
filtered_results = self._get_results(submission['results'])
|
|
scores = self._get_scores(submission['file_tree'])
|
|
for h, results in filtered_results.items():
|
|
if h in scores:
|
|
attribute, file_object = self._create_file_object(submission['file_infos'][h])
|
|
print(file_object)
|
|
for filename in scores[h]['name']:
|
|
file_object.add_attribute('filename', value=filename, **attribute)
|
|
for reference in self._parse_results(results):
|
|
file_object.add_reference(**reference)
|
|
self.misp_event.add_object(**file_object)
|
|
|
|
def _parse_results(self, results):
|
|
references = []
|
|
for result in results:
|
|
try:
|
|
attribute_type = self._results_mapping[result['type']]
|
|
except KeyError:
|
|
continue
|
|
references.append(self._create_attribute(result, attribute_type))
|
|
return references
|
|
|
|
|
|
def parse_config(apiurl, user_id, config):
|
|
error = {"error": "Please provide your AssemblyLine API key or Password."}
|
|
if config.get('apikey'):
|
|
try:
|
|
return Client(apiurl, apikey=(user_id, config['apikey']), verify=config['verifyssl'])
|
|
except ClientError as e:
|
|
error['error'] = f'Error while initiating a connection with AssemblyLine: {e.__str__()}'
|
|
if config.get('password'):
|
|
try:
|
|
return Client(apiurl, auth=(user_id, config['password']))
|
|
except ClientError as e:
|
|
error['error'] = f'Error while initiating a connection with AssemblyLine: {e.__str__()}'
|
|
return error
|
|
|
|
|
|
def handler(q=False):
|
|
if q is False:
|
|
return False
|
|
request = json.loads(q)
|
|
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
if request['attribute']['type'] not in mispattributes['input']:
|
|
return {'error': 'Unsupported attribute type.'}
|
|
if not request.get('config'):
|
|
return {"error": "Missing configuration."}
|
|
if not request['config'].get('apiurl'):
|
|
return {"error": "No AssemblyLine server address provided."}
|
|
apiurl = request['config']['apiurl']
|
|
if not request['config'].get('user_id'):
|
|
return {"error": "Please provide your AssemblyLine User ID."}
|
|
user_id = request['config']['user_id']
|
|
client = parse_config(apiurl, user_id, request['config'])
|
|
if isinstance(client, dict):
|
|
return client
|
|
assemblyline_parser = AssemblyLineParser()
|
|
assemblyline_parser.get_submission(request['attribute'], client)
|
|
return assemblyline_parser.finalize_results()
|
|
|
|
|
|
def introspection():
|
|
return mispattributes
|
|
|
|
|
|
def version():
|
|
moduleinfo['config'] = moduleconfig
|
|
return moduleinfo
|