diff --git a/examples/add_fail2ban_object.py b/examples/add_fail2ban_object.py index ea9cb1f..ccc014c 100755 --- a/examples/add_fail2ban_object.py +++ b/examples/add_fail2ban_object.py @@ -5,6 +5,9 @@ from pymisp import PyMISP, MISPEvent from pymisp.tools import Fail2BanObject import argparse from base64 import b64decode +from datetime import date, datetime +from dateutil.parser import parse + try: from keys import misp_url, misp_key, misp_verifycert @@ -14,29 +17,52 @@ except Exception: misp_key = True +def create_new_event(): + me = MISPEvent() + me.info = "Fail2Ban blocking" + me.add_tag(args.tag) + start = datetime.now() + me.add_attribute('datetime', start.isoformat(), comment='Start Time') + return me + + if __name__ == '__main__': parser = argparse.ArgumentParser(description='Add Fail2ban object.') parser.add_argument("-b", "--banned_ip", required=True, help="Banned IP address.") parser.add_argument("-a", "--attack_type", required=True, help="Type of attack.") + parser.add_argument("-t", "--tag", required=True, help="Tag to search on MISP.") parser.add_argument("-p", "--processing_timestamp", help="Processing timestamp.") parser.add_argument("-f", "--failures", help="Amount of failures that lead to the ban.") parser.add_argument("-s", "--sensor", help="Sensor identifier.") parser.add_argument("-v", "--victim", help="Victim identifier.") parser.add_argument("-l", "--logline", help="Logline (base64 encoded).") - parser.add_argument("-ap", "--aggregation_period", required=True, help="Max time of the event (1d, 1h, ...).") - parser.add_argument("-t", "--tag", required=True, help="Tag to search on MISP.") + parser.add_argument("-n", "--force_new", action='store_true', default=False, help="Force new MISP event.") + parser.add_argument("-d", "--disable_new", action='store_true', default=False, help="Do not create a new Event.") args = parser.parse_args() pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) - - response = pymisp.search(tags=args.tag, last=args.aggregation_period, published=False) - me = MISPEvent() - if 'response' in response and response['response']: - me.load(response['response'][0]) + event_id = -1 + me = None + if args.force_new: + me = create_new_event() else: - me.info = "Fail2Ban blocking" - me.add_tag(args.tag) - parameters = {'banned-ip': args.banned_ip, 'attack-type': args.attack_type, 'processing-timestamp': args.processing_timestamp} + response = pymisp.search_index(tag=args.tag, timestamp='1h') + if response['response']: + if args.disable_new: + event_id = response['response'][0]['id'] + else: + last_event_date = parse(response['response'][0]['date']).date() + nb_attr = response['response'][0]['attribute_count'] + if last_event_date < date.today() or int(nb_attr) > 1000: + me = create_new_event() + else: + event_id = response['response'][0]['id'] + else: + me = create_new_event() + + parameters = {'banned-ip': args.banned_ip, 'attack-type': args.attack_type} + if args.processing_timestamp: + parameters['processing-timestamp'] = args.processing_timestamp if args.failures: parameters['failures'] = args.failures if args.sensor: @@ -46,5 +72,9 @@ if __name__ == '__main__': if args.logline: parameters['logline'] = b64decode(args.logline).decode() f2b = Fail2BanObject(parameters=parameters, standalone=False) - me.add_object(f2b) - pymisp.add_event(me) + if me: + me.add_object(f2b) + pymisp.add_event(me) + elif event_id: + template_id = pymisp.get_object_template_id(f2b.template_uuid) + a = pymisp.add_object(event_id, template_id, f2b) diff --git a/pymisp/api.py b/pymisp/api.py index 0920bbc..1062290 100644 --- a/pymisp/api.py +++ b/pymisp/api.py @@ -953,7 +953,8 @@ class PyMISP(object): def search_index(self, published=None, eventid=None, tag=None, datefrom=None, dateuntil=None, eventinfo=None, threatlevel=None, distribution=None, - analysis=None, attribute=None, org=None, async_callback=None, normalize=False): + analysis=None, attribute=None, org=None, async_callback=None, normalize=False, + timestamp=None): """Search only at the index level. Use ! infront of value as NOT, default OR If using async, give a callback that takes 2 args, session and response: basic usage is @@ -971,11 +972,12 @@ class PyMISP(object): :param org: Organisation(s) | str or list :param async_callback: Function to call when the request returns (if running async) :param normalize: Normalize output | True or False + :param timestamp: Interval since last update (in second, or 1d, 1h, ...) """ allowed = {'published': published, 'eventid': eventid, 'tag': tag, 'Dateuntil': dateuntil, 'Datefrom': datefrom, 'eventinfo': eventinfo, 'threatlevel': threatlevel, 'distribution': distribution, 'analysis': analysis, 'attribute': attribute, - 'org': org} + 'org': org, 'timestamp': timestamp} rule_levels = {'distribution': ["0", "1", "2", "3", "!0", "!1", "!2", "!3"], 'threatlevel': ["1", "2", "3", "4", "!1", "!2", "!3", "!4"], 'analysis': ["0", "1", "2", "!0", "!1", "!2"]} @@ -1344,7 +1346,7 @@ class PyMISP(object): s = MISPSighting() s.from_dict(value=value, uuid=uuid, id=id, source=source, type=type, timestamp=timestamp, **kwargs) return self.set_sightings(s) - + def sighting_list(self, element_id, scope="attribute", org_id=False): """Get the list of sighting. :param element_id: could be an event id or attribute id @@ -1352,9 +1354,9 @@ class PyMISP(object): :param scope: could be attribute or event :return: A json list of sighting corresponding to the search :rtype: list - + :Example: - + >>> misp.sighting_list(4731) # default search on attribute [ ... ] >>> misp.sighting_list(42, event) # return list of sighting for event 42 @@ -1370,11 +1372,7 @@ class PyMISP(object): raise Exception('Invalid parameter, org_id must be a number') else: org_id = "" - uri = 'sightings/listSightings/{}/{}/{}'.format( - element_id, - scope, - org_id - ) + uri = 'sightings/listSightings/{}/{}/{}'.format(element_id, scope, org_id) url = urljoin(self.root_url, uri) response = self.__prepare_request('POST', url) return self._check_response(response) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 454c87f..8e718ea 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -452,7 +452,7 @@ class MISPEvent(AbstractMISP): def from_dict(self, **kwargs): # Required value self.info = kwargs.pop('info', None) - if not self.info: + if self.info is None: raise NewAttributeError('The info field of the new event is required.') # Default values for a valid event to send to a MISP instance diff --git a/setup.py b/setup.py index 5d7391d..6011137 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,8 @@ setup( extras_require={'fileobjects': ['lief>=0.8', 'python-magic'], 'neo': ['py2neo'], 'openioc': ['beautifulsoup4'], - 'virustotal': ['validators']}, + 'virustotal': ['validators'], + 'warninglists': ['pymispwarninglists']}, tests_require=[ 'jsonschema', 'python-dateutil',