mirror of https://github.com/CIRCL/lookyloo
				
				
				
			chg: Properly handle capture parents, avoid duplicates
							parent
							
								
									b735cbeae3
								
							
						
					
					
						commit
						5ee62d157f
					
				|  | @ -1045,7 +1045,7 @@ class Lookyloo(): | |||
|         obj.add_reference(vt_obj, 'analysed-with') | ||||
|         return vt_obj | ||||
| 
 | ||||
|     def misp_export(self, capture_uuid: str, with_parent: bool=False) -> Union[List[MISPEvent], MISPEvent, Dict[str, str]]: | ||||
|     def misp_export(self, capture_uuid: str, with_parent: bool=False) -> Union[List[MISPEvent], Dict[str, str]]: | ||||
|         '''Export a capture in MISP format. You can POST the return of this method | ||||
|         directly to a MISP instance and it will create an event.''' | ||||
|         cache = self.capture_cache(capture_uuid) | ||||
|  | @ -1092,6 +1092,20 @@ class Lookyloo(): | |||
| 
 | ||||
|         for u_object in redirects: | ||||
|             event.add_object(u_object) | ||||
|         final_redirect = event.objects[-1] | ||||
| 
 | ||||
|         screenshot: MISPAttribute = event.add_attribute('attachment', 'screenshot_landing_page.png', data=self.get_screenshot(capture_uuid), disable_correlation=True)  # type: ignore | ||||
|         try: | ||||
|             fo = FileObject(pseudofile=ct.root_hartree.rendered_node.body, filename=ct.root_hartree.rendered_node.filename) | ||||
|             fo.comment = 'Content received for the final redirect (before rendering)' | ||||
|             fo.add_reference(final_redirect, 'loaded-by', 'URL loading that content') | ||||
|             fo.add_reference(screenshot, 'rendered-as', 'Screenshot of the page') | ||||
|             event.add_object(fo) | ||||
|         except Har2TreeError: | ||||
|             pass | ||||
|         except AttributeError: | ||||
|             # No `body` in rendered node | ||||
|             pass | ||||
| 
 | ||||
|         if self.vt.available: | ||||
|             for e_obj in event.objects: | ||||
|  | @ -1101,34 +1115,17 @@ class Lookyloo(): | |||
|                 if vt_obj: | ||||
|                     event.add_object(vt_obj) | ||||
| 
 | ||||
|         screenshot: MISPAttribute = event.add_attribute('attachment', 'screenshot_landing_page.png', data=self.get_screenshot(capture_uuid), disable_correlation=True)  # type: ignore | ||||
|         try: | ||||
|             fo = FileObject(pseudofile=ct.root_hartree.rendered_node.body, filename=ct.root_hartree.rendered_node.filename) | ||||
|             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(screenshot, 'rendered-as', 'Screenshot of the page') | ||||
|             event.add_object(fo) | ||||
|         except Har2TreeError: | ||||
|             pass | ||||
|         except AttributeError: | ||||
|             # No `body` in rendered node | ||||
|             pass | ||||
| 
 | ||||
|         if with_parent and cache.parent: | ||||
|             parent = self.misp_export(cache.parent, with_parent) | ||||
|             if isinstance(parent, dict): | ||||
|                 # Something bad happened | ||||
|                 return parent | ||||
|             if isinstance(parent, list): | ||||
|                 # The parent has a parent | ||||
|                 event.extends_uuid = parent[-1].uuid | ||||
|                 parent.append(event) | ||||
|                 return parent | ||||
|             else: | ||||
|                 event.extends_uuid = parent.uuid | ||||
|                 return [parent, event] | ||||
| 
 | ||||
|         return event | ||||
|             event.extends_uuid = parent[-1].uuid | ||||
|             parent.append(event) | ||||
|             return parent | ||||
| 
 | ||||
|         return [event] | ||||
| 
 | ||||
|     def get_hashes(self, tree_uuid: str, hostnode_uuid: Optional[str]=None, urlnode_uuid: Optional[str]=None) -> Set[str]: | ||||
|         """Return hashes of resources. | ||||
|  |  | |||
|  | @ -53,23 +53,67 @@ class MISP(): | |||
|     def get_fav_tags(self): | ||||
|         return self.client.tags(pythonify=True, favouritesOnly=1) | ||||
| 
 | ||||
|     def push(self, event: MISPEvent) -> Union[MISPEvent, Dict]: | ||||
|         if self.available and self.enable_push: | ||||
|     def _prepare_push(self, to_push: Union[List[MISPEvent], MISPEvent], allow_duplicates: bool=False) -> Union[List[MISPEvent], MISPEvent, Dict]: | ||||
|         '''Adds the pre-configured information as required by the instance. | ||||
|         If duplicates aren't allowed, they will be automatically skiped and the | ||||
|         extends_uuid key in the next element in the list updated''' | ||||
|         if isinstance(to_push, MISPEvent): | ||||
|             events = [to_push] | ||||
|         else: | ||||
|             events = to_push | ||||
|         events_to_push = [] | ||||
|         existing_uuid_to_extend = None | ||||
|         for event in events: | ||||
|             if not allow_duplicates: | ||||
|                 existing_event = self.get_existing_event(event.attributes[0].value) | ||||
|                 if existing_event: | ||||
|                     existing_uuid_to_extend = existing_event.uuid | ||||
|                     continue | ||||
|             if existing_uuid_to_extend: | ||||
|                 event.extends_uuid = existing_uuid_to_extend | ||||
|                 existing_uuid_to_extend = None | ||||
| 
 | ||||
|             for tag in self.default_tags: | ||||
|                 event.add_tag(tag) | ||||
|             if self.auto_publish: | ||||
|                 event.publish() | ||||
|             return self.client.add_event(event, pythonify=True) | ||||
|             events_to_push.append(event) | ||||
|         return events_to_push | ||||
| 
 | ||||
|     def push(self, to_push: Union[List[MISPEvent], MISPEvent], allow_duplicates: bool=False) -> Union[List[MISPEvent], Dict]: | ||||
|         if self.available and self.enable_push: | ||||
|             events = self._prepare_push(to_push, allow_duplicates) | ||||
|             if not events: | ||||
|                 return {'error': 'All the events are already on the MISP instance.'} | ||||
|             if isinstance(events, Dict): | ||||
|                 return {'error': events} | ||||
|             to_return = [] | ||||
|             for event in events: | ||||
|                 new_event = self.client.add_event(event, pythonify=True) | ||||
|                 if isinstance(new_event, MISPEvent): | ||||
|                     to_return.append(new_event) | ||||
|                 else: | ||||
|                     return {'error': new_event} | ||||
|             return to_return | ||||
|         else: | ||||
|             return {'error': 'Module not available or push not enabled.'} | ||||
| 
 | ||||
|     def get_existing_event(self, permaurl: str) -> Optional[str]: | ||||
|     def get_existing_event_url(self, permaurl: str) -> Optional[str]: | ||||
|         attributes = self.client.search('attributes', value=permaurl, limit=1, page=1, pythonify=True) | ||||
|         if not attributes or not isinstance(attributes[0], MISPAttribute): | ||||
|             return None | ||||
|         url = f'{self.client.root_url}/events/{attributes[0].event_id}' | ||||
|         return url | ||||
| 
 | ||||
|     def get_existing_event(self, permaurl: str) -> Optional[MISPEvent]: | ||||
|         attributes = self.client.search('attributes', value=permaurl, limit=1, page=1, pythonify=True) | ||||
|         if not attributes or not isinstance(attributes[0], MISPAttribute): | ||||
|             return None | ||||
|         event = self.client.get_event(attributes[0].event_id, pythonify=True) | ||||
|         if isinstance(event, MISPEvent): | ||||
|             return event | ||||
|         return None | ||||
| 
 | ||||
| 
 | ||||
| class UniversalWhois(): | ||||
| 
 | ||||
|  |  | |||
|  | @ -307,7 +307,7 @@ hyperframe = ">=5.2.0,<6" | |||
| 
 | ||||
| [[package]] | ||||
| name = "har2tree" | ||||
| version = "1.5.5" | ||||
| version = "1.5.6" | ||||
| description = "HTTP Archive (HAR) to ETE Toolkit generator" | ||||
| category = "main" | ||||
| optional = false | ||||
|  | @ -762,7 +762,7 @@ docs = ["Sphinx (>=3.5.3,<4.0.0)", "myst-parser (>=0.13.5,<0.14.0)"] | |||
| 
 | ||||
| [[package]] | ||||
| name = "pymisp" | ||||
| version = "2.4.142" | ||||
| version = "2.4.143" | ||||
| description = "Python API for MISP." | ||||
| category = "main" | ||||
| optional = false | ||||
|  | @ -1365,8 +1365,8 @@ h2 = [ | |||
|     {file = "h2-3.2.0.tar.gz", hash = "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"}, | ||||
| ] | ||||
| har2tree = [ | ||||
|     {file = "har2tree-1.5.5-py3-none-any.whl", hash = "sha256:df666eb01228e112f6fb88622a7bb7dcd31713a727a63ea357b1e0c1466aa31a"}, | ||||
|     {file = "har2tree-1.5.5.tar.gz", hash = "sha256:3c49d6e487d7e3d0683de9318cae66bf2ad4389508514bebda1d131cc673c06c"}, | ||||
|     {file = "har2tree-1.5.6-py3-none-any.whl", hash = "sha256:94dcb58890d1e76a595e965cda8e3dd841b169a69f6d4fc84b7f1d869e65b11a"}, | ||||
|     {file = "har2tree-1.5.6.tar.gz", hash = "sha256:a1c578552669688c3899d85ca3ac96c0b8e68860c161e49b99ac797a8e054bb2"}, | ||||
| ] | ||||
| hpack = [ | ||||
|     {file = "hpack-3.0.0-py2.py3-none-any.whl", hash = "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89"}, | ||||
|  | @ -1742,8 +1742,8 @@ pylookyloo = [ | |||
|     {file = "pylookyloo-1.5.0.tar.gz", hash = "sha256:2f616e1f05206d84f4695b517b5c851775af69ac9df2e4f6fa880b6a2c6b6ec8"}, | ||||
| ] | ||||
| pymisp = [ | ||||
|     {file = "pymisp-2.4.142-py3-none-any.whl", hash = "sha256:677046cdb7b8f89b2cccd925377c136d6dfce63c8c44e6ca4ff6b342babe28a4"}, | ||||
|     {file = "pymisp-2.4.142.tar.gz", hash = "sha256:172c7bfa1bc907c37682bce7054c2147f132781a0019f082e71871f46186b23f"}, | ||||
|     {file = "pymisp-2.4.143-py3-none-any.whl", hash = "sha256:ceb6029045cfd2ab803902dc82e761909ca5337c2f6df484aef4e067e2f08d82"}, | ||||
|     {file = "pymisp-2.4.143.tar.gz", hash = "sha256:a51cddc4f274ec8daf7ccd0dd1fd599ee755ed77d168e72dfad01a41823877e3"}, | ||||
| ] | ||||
| pyopenssl = [ | ||||
|     {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"}, | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ from datetime import datetime, timedelta, timezone | |||
| import json | ||||
| import http | ||||
| import calendar | ||||
| from typing import Optional, Dict, Any, Union | ||||
| from typing import Optional, Dict, Any, Union, List | ||||
| import logging | ||||
| import hashlib | ||||
| from urllib.parse import quote_plus, unquote_plus | ||||
|  | @ -913,54 +913,50 @@ def web_misp_push_view(tree_uuid: str): | |||
|     error = False | ||||
|     if not lookyloo.misp.available: | ||||
|         flash('MISP module not available.', 'error') | ||||
|         error = True | ||||
|         return redirect(url_for('tree', tree_uuid=tree_uuid)) | ||||
|     elif not lookyloo.misp.enable_push: | ||||
|         flash('Push not enabled in MISP module.', 'error') | ||||
|         error = True | ||||
|         return redirect(url_for('tree', tree_uuid=tree_uuid)) | ||||
|     else: | ||||
|         event = lookyloo.misp_export(tree_uuid) | ||||
|         if isinstance(event, dict): | ||||
|             flash(f'Unable to generate the MISP export: {event}', 'error') | ||||
|             error = True | ||||
|     if error: | ||||
|         return redirect(url_for('tree', tree_uuid=tree_uuid)) | ||||
| 
 | ||||
|     # After this point, event is a MISPEvent | ||||
|             return redirect(url_for('tree', tree_uuid=tree_uuid)) | ||||
| 
 | ||||
|     if request.method == 'POST': | ||||
|         # event is a MISPEvent at this point | ||||
|         # Submit the event | ||||
|         tags = request.form.getlist('tags') | ||||
|         for tag in tags: | ||||
|             event.add_tag(tag)  # type: ignore | ||||
| 
 | ||||
|         error = False | ||||
|         events: List[MISPEvent] = [] | ||||
|         with_parents = request.form.get('with_parents') | ||||
|         if with_parents: | ||||
|             exports = lookyloo.misp_export(tree_uuid, True) | ||||
|             if isinstance(exports, dict): | ||||
|                 flash(f'Unable to create event: {exports}', 'error') | ||||
|                 error = True | ||||
|             elif isinstance(exports, MISPEvent): | ||||
|                 events = [exports] | ||||
|             else: | ||||
|                 events = exports | ||||
|         else: | ||||
|             events = [event]  # type: ignore | ||||
|             events = event | ||||
| 
 | ||||
|         if error: | ||||
|             return redirect(url_for('tree', tree_uuid=tree_uuid)) | ||||
| 
 | ||||
|         for event in events: | ||||
|             event = lookyloo.misp.push(event)  # type: ignore | ||||
|             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') | ||||
|         for e in events: | ||||
|             for tag in tags: | ||||
|                 e.add_tag(tag) | ||||
| 
 | ||||
|         new_events = lookyloo.misp.push(events) | ||||
|         if isinstance(new_events, dict): | ||||
|             flash(f'Unable to create event(s): {new_events}', 'error') | ||||
|         else: | ||||
|             for e in new_events: | ||||
|                 flash(f'MISP event {e.id} created on {lookyloo.misp.client.root_url}', 'success') | ||||
|         return redirect(url_for('tree', tree_uuid=tree_uuid)) | ||||
|     else: | ||||
|         # the 1st attribute in the event is the link to lookyloo | ||||
|         existing_misp_url = lookyloo.misp.get_existing_event(event.attributes[0].value)  # type: ignore | ||||
|         existing_misp_url = lookyloo.misp.get_existing_event_url(event[-1].attributes[0].value) | ||||
| 
 | ||||
|     fav_tags = lookyloo.misp.get_fav_tags() | ||||
|     cache = lookyloo.capture_cache(tree_uuid) | ||||
|  | @ -1018,43 +1014,41 @@ def misp_export(tree_uuid: str): | |||
|     event = lookyloo.misp_export(tree_uuid, True if with_parents else False) | ||||
|     if isinstance(event, dict): | ||||
|         return jsonify(event) | ||||
|     elif isinstance(event, list): | ||||
|         to_return = [] | ||||
|         for e in event: | ||||
|             to_return.append(e.to_json(indent=2)) | ||||
|         return jsonify(to_return) | ||||
|     else: | ||||
|         return Response(event.to_json(indent=2), mimetype='application/json') | ||||
| 
 | ||||
|     to_return = [] | ||||
|     for e in event: | ||||
|         to_return.append(e.to_json(indent=2)) | ||||
|     return jsonify(to_return) | ||||
| 
 | ||||
| 
 | ||||
| @app.route('/json/<string:tree_uuid>/misp_push', methods=['GET']) | ||||
| @app.route('/json/<string:tree_uuid>/misp_push', methods=['GET', 'POST']) | ||||
| @flask_login.login_required | ||||
| def misp_push(tree_uuid: str): | ||||
|     if request.method == 'POST': | ||||
|         parameters: Dict = request.get_json(force=True)  # type: ignore | ||||
|         with_parents = True if 'with_parents' in parameters else False | ||||
|         allow_duplicates = True if 'allow_duplicates' in parameters else False | ||||
|     else: | ||||
|         with_parents = False | ||||
|         allow_duplicates = False | ||||
|     to_return: Dict = {} | ||||
|     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) | ||||
|         event = lookyloo.misp_export(tree_uuid, with_parents) | ||||
|         if isinstance(event, dict): | ||||
|             to_return['error'] = event | ||||
|         elif isinstance(event, MISPEvent): | ||||
|             event = lookyloo.misp.push(event) | ||||
|             if isinstance(event, MISPEvent): | ||||
|                 return Response(event.to_json(indent=2), mimetype='application/json') | ||||
|             else: | ||||
|                 to_return['error'] = event | ||||
|         else: | ||||
|             events_to_return = [] | ||||
|             for e in event: | ||||
|                 me = lookyloo.misp.push(e) | ||||
|                 if isinstance(me, MISPEvent): | ||||
|                     events_to_return.append(me.to_json(indent=2)) | ||||
|                 else: | ||||
|                     to_return['error'] = me | ||||
|                     break | ||||
|             jsonify(events_to_return) | ||||
|             new_events = lookyloo.misp.push(event, allow_duplicates) | ||||
|             if isinstance(new_events, dict): | ||||
|                 to_return['error'] = new_events | ||||
|             else: | ||||
|                 events_to_return = [] | ||||
|                 for e in new_events: | ||||
|                     events_to_return.append(e.to_json(indent=2)) | ||||
|                 jsonify(events_to_return) | ||||
| 
 | ||||
|     return jsonify(to_return) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Raphaël Vinot
						Raphaël Vinot