new: Add push to MISP feature

pull/165/head
Raphaël Vinot 2021-01-28 18:37:44 +01:00
parent 393d075b2e
commit acfb0d1c26
4 changed files with 88 additions and 8 deletions

View File

@ -13,6 +13,7 @@
"MISP": { "MISP": {
"apikey": null, "apikey": null,
"url": "https://misp.url", "url": "https://misp.url",
"verify_tls_cert": true,
"enable_lookup": false, "enable_lookup": false,
"enable_push": false "enable_push": false
}, },

View File

@ -35,7 +35,7 @@ from .exceptions import NoValidHarFile, MissingUUID, LookylooException
from .helpers import (get_homedir, get_socket_path, load_cookies, get_config, from .helpers import (get_homedir, get_socket_path, load_cookies, get_config,
safe_create_dir, get_email_template, load_pickle_tree, safe_create_dir, get_email_template, load_pickle_tree,
remove_pickle_tree, get_resources_hashes, get_taxonomies, uniq_domains) remove_pickle_tree, get_resources_hashes, get_taxonomies, uniq_domains)
from .modules import VirusTotal, SaneJavaScript, PhishingInitiative from .modules import VirusTotal, SaneJavaScript, PhishingInitiative, MISP
from .capturecache import CaptureCache from .capturecache import CaptureCache
from .context import Context from .context import Context
from .indexing import Indexing from .indexing import Indexing
@ -75,6 +75,10 @@ class Lookyloo():
if not self.sanejs.available: if not self.sanejs.available:
self.logger.warning('Unable to setup the SaneJS module') self.logger.warning('Unable to setup the SaneJS module')
self.misp = MISP(get_config('modules', 'MISP'))
if not self.misp.available:
self.logger.warning('Unable to setup the MISP module')
self.context = Context(self.sanejs) self.context = Context(self.sanejs)
if not self.redis.exists('cache_loaded'): if not self.redis.exists('cache_loaded'):
@ -912,27 +916,29 @@ class Lookyloo():
event = MISPEvent() event = MISPEvent()
event.info = f'Lookyloo Capture ({cache.url})' event.info = f'Lookyloo Capture ({cache.url})'
event.add_attribute('link', f'https://{self.public_domain}/tree/{capture_uuid}') lookyloo_link = event.add_attribute('link', f'https://{self.public_domain}/tree/{capture_uuid}')
initial_url = URLObject(cache.url) initial_url = URLObject(cache.url)
redirects = [URLObject(url) for url in cache.redirects] redirects = [URLObject(url) for url in cache.redirects if url != cache.url]
if redirects: if redirects:
initial_url.add_reference(redirects[0], 'redirects-to') prec_object = initial_url
prec_object = redirects[0] for u_object in redirects:
for u_object in redirects[1:]:
prec_object.add_reference(u_object, 'redirects-to') prec_object.add_reference(u_object, 'redirects-to')
prec_object = u_object prec_object = u_object
event.add_object(initial_url) initial_obj = event.add_object(initial_url)
initial_obj.add_reference(lookyloo_link, 'captured-by', 'Capture on lookyloo')
for u_object in redirects: for u_object in redirects:
event.add_object(u_object) event.add_object(u_object)
event.add_attribute('attachment', 'screenshot_landing_page.png', data=self.get_screenshot(capture_uuid)) screenshot = event.add_attribute('attachment', 'screenshot_landing_page.png', data=self.get_screenshot(capture_uuid), disable_correlation=True)
try: try:
fo = FileObject(pseudofile=ct.root_hartree.rendered_node.body, filename='body_response.html') fo = FileObject(pseudofile=ct.root_hartree.rendered_node.body, filename='body_response.html')
fo.comment = 'Content received for the final redirect (before rendering)' fo.comment = 'Content received for the final redirect (before rendering)'
fo.add_reference(event.objects[-1], 'loaded-by', 'URL loading that content') fo.add_reference(event.objects[-1], 'loaded-by', 'URL loading that content')
fo.add_reference(screenshot, 'rendered-as', 'Screenshot of the page')
event.add_object(fo) event.add_object(fo)
except Har2TreeError: except Har2TreeError:
pass pass

View File

@ -16,6 +16,32 @@ from .exceptions import ConfigError
import vt # type: ignore import vt # type: ignore
from pysanejs import SaneJS from pysanejs import SaneJS
from pyeupi import PyEUPI from pyeupi import PyEUPI
from pymisp import PyMISP, MISPEvent
class MISP():
def __init__(self, config: Dict[str, Any]):
if not config.get('apikey'):
self.available = False
return
self.available = True
self.enable_lookup = False
self.enable_push = False
self.client = PyMISP(url=config['url'], key=config['apikey'], ssl=config['verify_tls_cert'])
if config.get('enable_lookup'):
self.enable_lookup = True
if config.get('enable_push'):
self.enable_push = True
self.storage_dir_misp = get_homedir() / 'misp'
self.storage_dir_misp.mkdir(parents=True, exist_ok=True)
def push(self, event: MISPEvent) -> Union[MISPEvent, Dict]:
if self.available and self.enable_push:
return self.client.add_event(event, pythonify=True)
else:
return {'error': 'Module not available or push not enabled.'}
class SaneJavaScript(): class SaneJavaScript():

View File

@ -15,6 +15,8 @@ from flask import Flask, render_template, request, send_file, redirect, url_for,
from flask_bootstrap import Bootstrap # type: ignore from flask_bootstrap import Bootstrap # type: ignore
from flask_httpauth import HTTPDigestAuth # type: ignore from flask_httpauth import HTTPDigestAuth # type: ignore
from pymisp import MISPEvent
from lookyloo.helpers import get_homedir, update_user_agents, get_user_agents, get_config, get_taxonomies from lookyloo.helpers import get_homedir, update_user_agents, get_user_agents, get_config, get_taxonomies
from lookyloo.lookyloo import Lookyloo, Indexing from lookyloo.lookyloo import Lookyloo, Indexing
from lookyloo.exceptions import NoValidHarFile, MissingUUID from lookyloo.exceptions import NoValidHarFile, MissingUUID
@ -723,6 +725,27 @@ def add_context(tree_uuid: str, node_uuid: str):
return redirect(url_for('ressources')) return redirect(url_for('ressources'))
@app.route('/tree/<string:tree_uuid>/misp_push', methods=['GET'])
@auth.login_required
def web_misp_push(tree_uuid: str):
if not lookyloo.misp.available:
flash('MISP module not available.', 'error')
elif not lookyloo.misp.enable_push:
flash('Push not enabled in MISP module.', 'error')
else:
event = lookyloo.misp_export(tree_uuid)
if isinstance(event, dict):
flash(f'Unable to generate the MISP export: {event}', 'error')
else:
event = lookyloo.misp.push(event)
if isinstance(event, MISPEvent):
flash(f'MISP event {event.id} created on {lookyloo.misp.client.root_url}', 'success')
else:
flash(f'Unable to create event: {event}', 'error')
return redirect(url_for('tree', tree_uuid=tree_uuid))
# Query API # Query API
@app.route('/json/<string:tree_uuid>/redirects', methods=['GET']) @app.route('/json/<string:tree_uuid>/redirects', methods=['GET'])
@ -755,6 +778,30 @@ def misp_export(tree_uuid: str):
return Response(event.to_json(indent=2), mimetype='application/json') return Response(event.to_json(indent=2), mimetype='application/json')
@app.route('/json/<string:tree_uuid>/misp_push', methods=['GET'])
@auth.login_required
def misp_push(tree_uuid: str):
to_return = {}
if not lookyloo.misp.available:
to_return['error'] = 'MISP module not available.'
elif not lookyloo.misp.enable_push:
to_return['error'] = 'Push not enabled in MISP module.'
else:
event = lookyloo.misp_export(tree_uuid)
if isinstance(event, dict):
to_return['error'] = event
else:
event = lookyloo.misp.push(event)
if isinstance(event, MISPEvent):
to_return = event.to_json(indent=2)
else:
to_return['error'] = event
if isinstance(to_return, dict):
to_return = json.dumps(to_return, indent=2)
return Response(to_return, mimetype='application/json')
@app.route('/json/hash_info/<h>', methods=['GET']) @app.route('/json/hash_info/<h>', methods=['GET'])
def json_hash_info(h: str): def json_hash_info(h: str):
details, body = lookyloo.get_body_hash_full(h) details, body = lookyloo.get_body_hash_full(h)