diff --git a/bin/lib/Tag.py b/bin/lib/Tag.py index 215d28d2..a3a5f962 100755 --- a/bin/lib/Tag.py +++ b/bin/lib/Tag.py @@ -710,6 +710,13 @@ def delete_object_tag(tag, obj_type, id, subtype=''): r_tags.srem(f'tag:{obj_type}:{subtype}:{id}', tag) update_tag_global_by_obj_type(tag, obj_type, subtype=subtype) +def delete_object_tags(obj_type, subtype, obj_id): + if not subtype: + subtype = '' + for tag in get_object_tags(obj_type, obj_id, subtype=subtype): + delete_object_tag(tag, obj_type, obj_id, subtype=subtype) + + ################################################################################################################ # TODO: REWRITE OLD @@ -960,7 +967,10 @@ def is_galaxy_tag(tag, namespace=None): return False def is_custom_tag(tag): - return r_tags.sismember('tags:custom', tag) + try: + return r_tags.sismember('tags:custom', tag) + except: + return False # # TODO: # def is_valid_tag(tag): @@ -1018,6 +1028,20 @@ def sort_tags_taxonomies_galaxies(tags): galaxies_tags.append(tag) return taxonomies_tags, galaxies_tags +def sort_tags_taxonomies_galaxies_customs(tags): + taxonomies_tags = [] + galaxies_tags = [] + customs_tags = [] + for tag in tags: + if is_taxonomie_tag(tag): + taxonomies_tags.append(tag) + elif is_custom_tag(tag): + print() + customs_tags.append(tag) + else: + galaxies_tags.append(tag) + return taxonomies_tags, galaxies_tags, customs_tags + ##-- Taxonomies - Galaxies --## def is_tag_in_all_tag(tag): @@ -1089,8 +1113,9 @@ def get_modal_add_tags(object_id, object_type='item', object_subtype=''): ######## NEW VERSION ######## def create_custom_tag(tag): - r_tags.sadd('tags:custom', tag) - r_tags.sadd('tags:custom:enabled_tags', tag) + if not is_taxonomie_tag(tag) and not is_galaxy_tag(tag): + r_tags.sadd('tags:custom', tag) + r_tags.sadd('tags:custom:enabled_tags', tag) # # TODO: ADD color def get_tag_metadata(tag, r_int=False): diff --git a/bin/lib/Tracker.py b/bin/lib/Tracker.py index fcb222f8..db5b2cd5 100755 --- a/bin/lib/Tracker.py +++ b/bin/lib/Tracker.py @@ -97,23 +97,26 @@ class Tracker: def exists(self): return r_tracker.exists(f'tracker:{self.uuid}') + def _get_field(self, field): + return r_tracker.hget(f'tracker:{self.uuid}', field) + def _set_field(self, field, value): r_tracker.hset(f'tracker:{self.uuid}', field, value) def get_date(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'date') + return self._get_field('date') def get_last_change(self, r_str=False): - last_change = r_tracker.hget(f'tracker:{self.uuid}', 'last_change') + last_change = self._get_field('last_change') if r_str and last_change: last_change = datetime.datetime.fromtimestamp(float(last_change)).strftime('%Y-%m-%d %H:%M:%S') return last_change def get_first_seen(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'first_seen') + return self._get_field('first_seen') def get_last_seen(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'last_seen') + return self._get_field('last_seen') def _set_first_seen(self, date): self._set_field('first_seen', date) @@ -166,10 +169,10 @@ class Tracker: r_tracker.hdel(f'tracker:{self.uuid}', 'last_seen') def get_description(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'description') + return self._get_field('description') def get_level(self): - level = r_tracker.hget(f'tracker:{self.uuid}', 'level') + level = self._get_field('level') if not level: level = 0 return int(level) @@ -194,7 +197,7 @@ class Tracker: self._set_field('level', level) def get_filters(self): - filters = r_tracker.hget(f'tracker:{self.uuid}', 'filters') + filters = self._get_field('filters') if not filters: return {} else: @@ -205,20 +208,22 @@ class Tracker: self._set_field('filters', json.dumps(filters)) def get_tracked(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'tracked') + return self._get_field('tracked') def get_type(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'type') + return self._get_field('type') def get_tags(self): return r_tracker.smembers(f'tracker:tags:{self.uuid}') def _set_tags(self, tags): for tag in tags: - tag = escape(tag) r_tracker.sadd(f'tracker:tags:{self.uuid}', tag) Tag.create_custom_tag(tag) # TODO CUSTOM TAGS + def _del_tags(self): + return r_tracker.delete(f'tracker:tags:{self.uuid}') + def mail_export(self): return r_tracker.exists(f'tracker:mail:{self.uuid}') @@ -229,8 +234,11 @@ class Tracker: for mail in mails: r_tracker.sadd(f'tracker:mail:{self.uuid}', escape(mail)) + def _del_mails(self): + r_tracker.delete(f'tracker:mail:{self.uuid}') + def get_user(self): - return r_tracker.hget(f'tracker:{self.uuid}', 'user_id') + return self._get_field('user_id') def webhook_export(self): return r_tracker.hexists(f'tracker:mail:{self.uuid}', 'webhook') @@ -267,6 +275,8 @@ class Tracker: meta['level'] = self.get_level() if 'description' in options: meta['description'] = self.get_description() + if 'nb_objs' in options: + meta['nb_objs'] = self.get_nb_objs() if 'tags' in options: meta['tags'] = self.get_tags() if 'filters' in options: @@ -289,39 +299,28 @@ class Tracker: r_tracker.lpush('trackers:dashboard', mess) r_tracker.ltrim(f'trackers:dashboard', 0, 9) - # - TODO Data Retention TO Implement - # - # Or Daily/Monthly Global DB Cleanup: - # Iterate on each tracker: - # Iterate on each Obj: - # Iterate on each date: - # Delete from tracker range if date limit exceeded - # - TODO - def add(self, obj_type, subtype, obj_id, date=None): - if not subtype: - subtype = '' - if not date: - date = Date.get_today_date_str() - - new_obj_date = r_tracker.sadd(f'tracker:objs:{self.uuid}:{date}', f'{obj_type}:{subtype}:{obj_id}') - new_obj = r_tracker.sadd(f'obj:trackers:{obj_type}:{subtype}:{obj_id}', self.uuid) - # MATCHES - if new_obj: - r_tracker.zincrby(f'tracker:match:{self.uuid}', 1, 'total') - r_tracker.zincrby(f'tracker:match:{self.uuid}', 1, obj_type) - - # Only save date for daterange objects - Needed for the DB Cleaner - if obj_type != 'item': # not obj_date: - r_tracker.sadd(f'obj:tracker:{obj_type}:{subtype}:{obj_id}:{self.uuid}', date) - r_tracker.sadd(f'tracker:objs:{self.uuid}:{obj_type}', f'{subtype}:{obj_id}') - - if new_obj_date: - self.update_daterange(date) - - self._add_to_dashboard(obj_type, subtype, obj_id) + def get_nb_objs_by_type(self, obj_type): + return r_tracker.scard(f'tracker:objs:{self.uuid}:{obj_type}') def get_objs_by_type(self, obj_type): return r_tracker.smembers(f'tracker:objs:{self.uuid}:{obj_type}') + def get_nb_objs(self): + objs = {} + for obj_type in get_objects_tracked(): + nb = self.get_nb_objs_by_type(obj_type) + if nb: + objs[obj_type] = nb + return objs + + def get_objs(self): + objs = [] + for obj_type in get_objects_tracked(): + for obj in self.get_objs_by_type(obj_type): + subtype, obj_id = obj.split(':', 1) + objs.append((obj_type, subtype, obj_id)) + return objs + def get_nb_objs_by_date(self, date): return r_tracker.scard(f'tracker:objs:{self.uuid}:{date}') @@ -335,10 +334,32 @@ class Tracker: return objs def get_obj_dates(self, obj_type, subtype, obj_id): - if obj_type == 'item': - return [item_basic.get_item_date(obj_id)] - else: - return r_tracker.smembers(f'obj:tracker:{obj_type}:{subtype}:{obj_id}:{self.uuid}') + return r_tracker.smembers(f'obj:tracker:{obj_type}:{subtype}:{obj_id}:{self.uuid}') + + # - TODO Data Retention TO Implement - # + # Or Daily/Monthly Global DB Cleanup: + # Iterate on each tracker: + # Iterate on each Obj: + # Iterate on each date: + # Delete from tracker range if date limit exceeded + # - TODO + def add(self, obj_type, subtype, obj_id, date=None): + if not subtype: + subtype = '' + if not date: + date = Date.get_today_date_str() + + new_obj_date = r_tracker.sadd(f'tracker:objs:{self.uuid}:{date}', f'{obj_type}:{subtype}:{obj_id}') + r_tracker.sadd(f'obj:trackers:{obj_type}:{subtype}:{obj_id}', self.uuid) + + # Only save object match date - Needed for the DB Cleaner + r_tracker.sadd(f'obj:tracker:{obj_type}:{subtype}:{obj_id}:{self.uuid}', date) + r_tracker.sadd(f'tracker:objs:{self.uuid}:{obj_type}', f'{subtype}:{obj_id}') + + if new_obj_date: + self.update_daterange(date) + + self._add_to_dashboard(obj_type, subtype, obj_id) def remove(self, obj_type, subtype, obj_id): if not subtype: @@ -350,9 +371,6 @@ class Tracker: r_tracker.srem(f'obj:trackers:{obj_type}:{subtype}:{obj_id}', self.uuid) r_tracker.srem(f'tracker:objs:{self.uuid}', f'{obj_type}:{subtype}:{obj_id}') - # MATCHES - r_tracker.zincrby(f'tracker:match:{self.uuid}', -1, 'total') - r_tracker.zincrby(f'tracker:match:{self.uuid}', -1, obj_type) self.update_daterange() # TODO escape custom tags @@ -483,19 +501,28 @@ class Tracker: # Tags nb_old_tags = r_tracker.scard(f'tracker:tags:{self.uuid}') if nb_old_tags > 0 or tags: - r_tracker.delete(f'tracker:tags:{self.uuid}') + self._del_tags() self._set_tags(tags) # Mails nb_old_mails = r_tracker.scard(f'tracker:mail:{self.uuid}') if nb_old_mails > 0 or mails: - r_tracker.delete(f'tracker:mail:{self.uuid}') + self._del_mails() self._set_mails(mails) - nb_old_sources = r_tracker.scard(f'tracker:sources:{self.uuid}') # TODO FILTERS - if nb_old_sources > 0 or sources: - r_tracker.delete(f'tracker:sources:{self.uuid}') - self._set_sources(sources) + # Filters + if not filters: + filters = {} + for obj_type in get_objects_tracked(): + filters[obj_type] = {} + else: + self.set_filters(filters) + for obj_type in filters: + r_tracker.sadd(f'trackers:objs:{tracker_type}:{obj_type}', to_track) + r_tracker.sadd(f'trackers:uuid:{tracker_type}:{to_track}', f'{self.uuid}:{obj_type}') + if tracker_type != old_type: + r_tracker.srem(f'trackers:objs:{old_type}:{obj_type}', old_to_track) + r_tracker.srem(f'trackers:uuid:{old_type}:{old_to_track}', f'{self.uuid}:{obj_type}') # Refresh Trackers trigger_trackers_refresh(tracker_type) @@ -506,7 +533,37 @@ class Tracker: return self.uuid def delete(self): - pass + for obj in self.get_objs(): + self.remove(obj[0], obj[1], obj[2]) + + tracker_type = self.get_type() + tracked = self.get_tracked() + r_tracker.srem(f'all:tracker:{tracker_type}', tracked) + # tracker - uuid map + r_tracker.srem(f'all:tracker_uuid:{tracker_type}:{tracked}', self.uuid) + r_tracker.srem('trackers:all', self.uuid) + r_tracker.srem(f'trackers:all:{tracker_type}', self.uuid) + + if tracker_type == 'typosquatting': + r_tracker.delete(f'tracker:typosquatting:{tracked}') + elif tracker_type == 'yara': + if not is_default_yara_rule(tracked): + filepath = get_yara_rule_file_by_tracker_name(tracked) + if filepath: + os.remove(filepath) + + # Filters + filters = self.get_filters() + if not filters: + filters = get_objects_tracked() + for obj_type in filters: + r_tracker.srem(f'trackers:objs:{tracker_type}:{obj_type}', tracked) + r_tracker.srem(f'trackers:uuid:{tracker_type}:{tracked}', f'{self.uuid}:{obj_type}') + + self._del_mails() + self._del_tags() + # meta + r_tracker.delete(f'tracker:{self.uuid}') def create_tracker(tracker_type, to_track, user_id, level, description=None, filters={}, tags=[], mails=[], webhook=None, tracker_uuid=None): @@ -612,12 +669,14 @@ def get_trackers_dashboard(): tracker_uuid, timestamp, obj_type, subtype, obj_id = raw.split(':', 4) tracker = Tracker(tracker_uuid) meta = tracker.get_meta(options={'tags'}) + if not meta.get('type'): + meta['type'] = 'Tracker DELETED' timestamp = datetime.datetime.fromtimestamp(float(timestamp)).strftime('%Y-%m-%d %H:%M:%S') meta['timestamp'] = timestamp trackers.append(meta) return trackers -def get_user_dashboard(user_id): # TODO SORT + REMOVE OLDER ROWS +def get_user_dashboard(user_id): # TODO SORT + REMOVE OLDER ROWS (trim) trackers = [] for raw in r_tracker.lrange(f'trackers:user:{user_id}', 0, -1): tracker_uuid, timestamp, obj_type, subtype, obj_id = raw.split(':', 4) @@ -822,8 +881,8 @@ def api_add_tracker(dict_input, user_id): description = escape(description) webhook = dict_input.get('webhook', '') webhook = escape(webhook) - res = api_validate_tracker_to_add(to_track , tracker_type, nb_words=nb_words) - if res[1]!=200: + res = api_validate_tracker_to_add(to_track, tracker_type, nb_words=nb_words) + if res[1] != 200: return res to_track = res[0]['tracked'] tracker_type = res[0]['type'] @@ -878,23 +937,81 @@ def api_add_tracker(dict_input, user_id): return {'tracked': to_track, 'type': tracker_type, 'uuid': tracker_uuid}, 200 -# TODO def api_edit_tracker(dict_input, user_id): - pass - # tracker_uuid = dict_input.get('uuid', None) - # # check edit ACL - # if tracker_uuid: - # res = api_is_allowed_to_edit_tracker(tracker_uuid, user_id) - # if res[1] != 200: - # return res - # else: - # # check if tracker already tracked in global - # if level==1: - # if is_tracked_in_global_level(to_track, tracker_type) and not tracker_uuid: - # return {"status": "error", "reason": "Tracker already exist"}, 409 - # else: - # if is_tracked_in_user_level(to_track, tracker_type, user_id) and not tracker_uuid: - # return {"status": "error", "reason": "Tracker already exist"}, 409 + tracker_uuid = dict_input.get('uuid') + res = api_check_tracker_acl(tracker_uuid, user_id) + if res: + return res + + tracker = Tracker(tracker_uuid) + + to_track = dict_input.get('tracked', None) + if not to_track: + return {"status": "error", "reason": "Tracker not provided"}, 400 + tracker_type = dict_input.get('type', None) + if not tracker_type: + return {"status": "error", "reason": "Tracker type not provided"}, 400 + nb_words = dict_input.get('nb_words', 1) + description = dict_input.get('description', '') + description = escape(description) + webhook = dict_input.get('webhook', '') + webhook = escape(webhook) + res = api_validate_tracker_to_add(to_track, tracker_type, nb_words=nb_words) + if res[1] != 200: + return res + to_track = res[0]['tracked'] + tracker_type = res[0]['type'] + + tags = dict_input.get('tags', []) + mails = dict_input.get('mails', []) + res = verify_mail_list(mails) + if res: + return res + + # Filters # TODO MOVE ME + filters = dict_input.get('filters', {}) + if filters: + if filters.keys() == {'decoded', 'item', 'pgp'} and set(filters['pgp'].get('subtypes', [])) == {'mail', 'name'}: + if not filters['decoded'] and not filters['item']: + filters = {} + for obj_type in filters: + if obj_type not in get_objects_tracked(): + return {"status": "error", "reason": "Invalid Tracker Object type"}, 400 + + if obj_type == 'pgp': + if set(filters['pgp'].get('subtypes', [])) == {'mail', 'name'}: + filters['pgp'].pop('subtypes') + + for filter_name in filters[obj_type]: + if filter_name not in {'mimetypes', 'sources', 'subtypes'}: + return {"status": "error", "reason": "Invalid Filter"}, 400 + elif filter_name == 'mimetypes': # TODO + pass + elif filter_name == 'sources': + if obj_type == 'item': + res = item_basic.verify_sources_list(filters['item']['sources']) + if res: + return res + else: + return {"status": "error", "reason": "Invalid Filter sources"}, 400 + elif filter_name == 'subtypes': + obj_subtypes = set(get_object_all_subtypes(obj_type)) + for subtype in filters[obj_type]['subtypes']: + if subtype not in obj_subtypes: + return {"status": "error", "reason": "Invalid Tracker Object subtype"}, 400 + + level = dict_input.get('level', 1) + try: + level = int(level) + except TypeError: + level = 1 + if level not in range(0, 1): + level = 1 + + tracker.edit(tracker_type, to_track, level, description=description, filters=filters, + tags=tags, mails=mails, webhook=webhook) + return {'tracked': to_track, 'type': tracker_type, 'uuid': tracker_uuid}, 200 + def api_delete_tracker(data, user_id): tracker_uuid = data.get('uuid') @@ -1073,7 +1190,7 @@ def save_yara_rule(yara_rule_type, yara_rule, tracker_uuid=None): filename = os.path.join('custom-rules', tracker_uuid + '.yar') with open(os.path.join(get_yara_rules_dir(), filename), 'w') as f: f.write(str(yara_rule)) - if yara_rule_type == 'yara_default': + elif yara_rule_type == 'yara_default': filename = os.path.join('ail-yara-rules', 'rules', yara_rule) return filename @@ -1316,7 +1433,9 @@ class RetroHunt: def get_nb_objs(self): objs = {} for obj_type in get_objects_retro_hunted(): - objs[obj_type] = self.get_nb_objs_by_type(obj_type) + nb = self.get_nb_objs_by_type(obj_type) + if nb: + objs[obj_type] = nb return objs def get_objs(self): @@ -1363,12 +1482,6 @@ class RetroHunt: for mail in mails: r_tracker.sadd(f'retro_hunt:mails:{self.uuid}', escape(mail)) - # TODO Delete filters - SAVE DEFAULT OBJECTS ??? - # Filters - # if not filters: - # filters = {} - # for obj_type in get_objects_tracked(): - # filters[obj_type] = {} if filters: self.set_filters(filters) @@ -1379,11 +1492,17 @@ class RetroHunt: state = 'pending' self._set_state(state) - # TODO Delete Rule custom def delete(self): - if self.is_running(): + if self.is_running() and self.get_state() != 'completed': return None + # Delete custom rule + rule = self.get_rule() + if not is_default_yara_rule(rule): + filepath = get_yara_rule_file_by_tracker_name(rule) + if filepath: + os.remove(filepath) + r_tracker.srem('retro_hunts:pending', self.uuid) r_tracker.delete(f'retro_hunts:{self.uuid}') r_tracker.delete(f'retro_hunt:tags:{self.uuid}') @@ -1580,7 +1699,7 @@ def api_delete_retro_hunt_task(task_uuid): if res: return res retro_hunt = RetroHunt(task_uuid) - if retro_hunt.is_running(): + if retro_hunt.is_running() and retro_hunt.get_state() != 'completed': return {"status": "error", "reason": "You can't delete a running task"}, 400 else: return retro_hunt.delete(), 200 diff --git a/bin/trackers/Tracker_Regex.py b/bin/trackers/Tracker_Regex.py index 56de7696..091140bd 100755 --- a/bin/trackers/Tracker_Regex.py +++ b/bin/trackers/Tracker_Regex.py @@ -83,12 +83,7 @@ class Tracker_Regex(AbstractModule): print(f'new tracked regex found: {tracker_name} in {obj_id}') self.redis_logger.warning(f'new tracked regex found: {tracker_name} in {obj_id}') - if obj.get_type() == 'item': - date = obj.get_date() - else: - date = None - - tracker.add(obj.get_type(), obj.get_subtype(r_str=True), obj_id, date=date) + tracker.add(obj.get_type(), obj.get_subtype(r_str=True), obj_id) for tag in tracker.get_tags(): if obj.get_type() == 'item': diff --git a/bin/trackers/Tracker_Term.py b/bin/trackers/Tracker_Term.py index 1fabddce..c79d308e 100755 --- a/bin/trackers/Tracker_Term.py +++ b/bin/trackers/Tracker_Term.py @@ -124,14 +124,10 @@ class Tracker_Term(AbstractModule): if ail_objects.is_filtered(obj, filters): continue - print(f'new tracked term found: {tracker_name} in {obj_id}') + print(f'new tracked term {tracker_uuid} found: {tracker_name} in {obj_id}') self.redis_logger.warning(f'new tracked term found: {tracker_name} in {obj_id}') - if obj.get_type() == 'item': - date = obj.get_date() - else: - date = None - tracker.add(obj.get_type(), obj.get_subtype(), obj_id, date=date) + tracker.add(obj.get_type(), obj.get_subtype(), obj_id) # Tags for tag in tracker.get_tags(): diff --git a/bin/trackers/Tracker_Typo_Squatting.py b/bin/trackers/Tracker_Typo_Squatting.py index 0a573512..99cacee3 100755 --- a/bin/trackers/Tracker_Typo_Squatting.py +++ b/bin/trackers/Tracker_Typo_Squatting.py @@ -78,12 +78,7 @@ class Tracker_Typo_Squatting(AbstractModule): print(f'new tracked typosquatting found: {tracked} in {obj_id}') self.redis_logger.warning(f'tracker typosquatting: {tracked} in {obj_id}') - if obj.get_type() == 'item': - date = obj.get_date() - else: - date = None - - tracker.add(obj.get_type(), obj.get_subtype(r_str=True), obj_id, date=date) + tracker.add(obj.get_type(), obj.get_subtype(r_str=True), obj_id) # Tags for tag in tracker.get_tags(): diff --git a/bin/trackers/Tracker_Yara.py b/bin/trackers/Tracker_Yara.py index 98d7d4cf..7ffea007 100755 --- a/bin/trackers/Tracker_Yara.py +++ b/bin/trackers/Tracker_Yara.py @@ -84,12 +84,7 @@ class Tracker_Yara(AbstractModule): if ail_objects.is_filtered(self.obj, filters): continue - if self.obj.get_type() == 'item': - date = self.obj.get_date() - else: - date = None - - tracker.add(self.obj.get_type(), self.obj.get_subtype(r_str=True), obj_id, date=date) + tracker.add(self.obj.get_type(), self.obj.get_subtype(r_str=True), obj_id) # Tags for tag in tracker.get_tags(): diff --git a/var/www/blueprints/hunters.py b/var/www/blueprints/hunters.py index 77d8df12..08316d4f 100644 --- a/var/www/blueprints/hunters.py +++ b/var/www/blueprints/hunters.py @@ -9,7 +9,7 @@ import os import sys import json -from flask import render_template, jsonify, request, Blueprint, redirect, url_for, Response, escape +from flask import render_template, jsonify, request, Blueprint, redirect, url_for, Response, escape, abort from flask_login import login_required, current_user, login_user, logout_user sys.path.append('modules') @@ -27,6 +27,7 @@ from lib.objects import ail_objects from lib import item_basic from lib import Tracker from lib import Tag +from packages import Date bootstrap_label = Flask_config.bootstrap_label @@ -47,6 +48,14 @@ def create_json_response(data, status_code): # ============= ROUTES ============== +@hunters.route("/yara/rule/default/content", methods=['GET']) +@login_required +@login_read_only +def get_default_yara_rule_content(): + default_yara_rule = request.args.get('rule') + res = Tracker.api_get_default_rule_content(default_yara_rule) + return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] + ################## # TRACKERS # ################## @@ -120,98 +129,160 @@ def tracked_menu_typosquatting(): return render_template("trackersManagement.html", user_trackers=user_trackers, global_trackers=global_trackers, bootstrap_label=bootstrap_label, tracker_type=tracker_type) + +@hunters.route("/tracker/show") +@login_required +@login_read_only +def show_tracker(): + user_id = current_user.get_id() + tracker_uuid = request.args.get('uuid', None) + res = Tracker.api_is_allowed_to_edit_tracker(tracker_uuid, user_id) + if res[1] != 200: # invalid access + return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] + + date_from = request.args.get('date_from') + date_to = request.args.get('date_to') + + if date_from: + date_from = date_from.replace('-', '') + if date_to: + date_to = date_to.replace('-', '') + + tracker = Tracker.Tracker(tracker_uuid) + meta = tracker.get_meta(options={'description', 'level', 'mails', 'filters', 'sparkline', 'tags', + 'user', 'webhook', 'nb_objs'}) + + if meta['type'] == 'yara': + yara_rule_content = Tracker.get_yara_rule_content(meta['tracked']) + else: + yara_rule_content = None + + if meta['type'] == 'typosquatting': + typo_squatting = Tracker.get_tracked_typosquatting_domains(meta['tracked']) + sorted(typo_squatting) + else: + typo_squatting = set() + + if date_from: + date_from, date_to = Date.sanitise_daterange(meta['first_seen'], meta['last_seen']) + objs = tracker.get_objs_by_daterange(date_from, date_to) + meta['objs'] = ail_objects.get_objects_meta(objs, flask_context=True) + else: + date_from = '' + date_to = '' + meta['objs'] = [] + + meta['date_from'] = date_from + meta['date_to'] = date_to + meta['item_sources'] = sorted(meta['filters'].get('item', {}).get('sources', [])) + if meta['filters']: + meta['filters'] = json.dumps(meta['filters'], indent=4) + + return render_template("tracker_show.html", meta=meta, + rule_content=yara_rule_content, + typo_squatting=typo_squatting, + bootstrap_label=bootstrap_label) + +def parse_add_edit_request(request_form): + to_track = request_form.get("tracker") + tracker_uuid = request_form.get("tracker_uuid") + tracker_type = request_form.get("tracker_type") + nb_words = request_form.get("nb_word", 1) + description = request.form.get("description", '') + webhook = request_form.get("webhook", '') + level = request_form.get("level", 0) + mails = request_form.get("mails", []) + + # TAGS + tags = request_form.get("tags", []) + taxonomies_tags = request_form.get('taxonomies_tags') + if taxonomies_tags: + try: + taxonomies_tags = json.loads(taxonomies_tags) + except: + taxonomies_tags = [] + else: + taxonomies_tags = [] + galaxies_tags = request_form.get('galaxies_tags') + if galaxies_tags: + try: + galaxies_tags = json.loads(galaxies_tags) + except: + galaxies_tags = [] + else: + galaxies_tags = [] + # custom tags + if tags: + tags = tags.split() + else: + tags = [] + escaped = [] + for tag in tags: + escaped.append(tag) + tags = escaped + taxonomies_tags + galaxies_tags + + # YARA # + if tracker_type == 'yara': + yara_default_rule = request_form.get("yara_default_rule") + yara_custom_rule = request_form.get("yara_custom_rule") + if yara_custom_rule: + to_track = yara_custom_rule + tracker_type = 'yara_custom' + else: + to_track = yara_default_rule + tracker_type = 'yara_default' + + if level == 'on': + level = 1 + else: + level = 0 + if mails: + mails = mails.split() + else: + mails = [] + + # FILTERS + filters = {} + for obj_type in Tracker.get_objects_tracked(): + new_filter = request_form.get(f'{obj_type}_obj') + if new_filter == 'on': + filters[obj_type] = {} + # Mimetypes + mimetypes = request_form.get(f'mimetypes_{obj_type}', []) + if mimetypes: + mimetypes = json.loads(mimetypes) + filters[obj_type]['mimetypes'] = mimetypes + # Sources + sources = request_form.get(f'sources_{obj_type}', []) + if sources: + sources = json.loads(sources) + filters[obj_type]['sources'] = sources + # Subtypes + for obj_subtype in ail_core.get_object_all_subtypes(obj_type): + subtype = request_form.get(f'filter_{obj_type}_{obj_subtype}') + if subtype == 'on': + if 'subtypes' not in filters[obj_type]: + filters[obj_type]['subtypes'] = [] + filters[obj_type]['subtypes'].append(obj_subtype) + + input_dict = {"tracked": to_track, "type": tracker_type, + "tags": tags, "mails": mails, "filters": filters, + "level": level, "description": description, "webhook": webhook} + if tracker_uuid: + input_dict['uuid'] = tracker_uuid + if tracker_type == 'set': + try: + input_dict['nb_words'] = int(nb_words) + except (ValueError, TypeError): + input_dict['nb_words'] = 1 + return input_dict + @hunters.route("/tracker/add", methods=['GET', 'POST']) @login_required @login_analyst def add_tracked_menu(): if request.method == 'POST': - to_track = request.form.get("tracker") - tracker_uuid = request.form.get("tracker_uuid") - tracker_type = request.form.get("tracker_type") - nb_words = request.form.get("nb_word", 1) - description = request.form.get("description", '') - webhook = request.form.get("webhook", '') - level = request.form.get("level", 0) - mails = request.form.get("mails", []) - - # TAGS - tags = request.form.get("tags", []) - taxonomies_tags = request.form.get('taxonomies_tags') - if taxonomies_tags: - try: - taxonomies_tags = json.loads(taxonomies_tags) - except: - taxonomies_tags = [] - else: - taxonomies_tags = [] - galaxies_tags = request.form.get('galaxies_tags') - if galaxies_tags: - try: - galaxies_tags = json.loads(galaxies_tags) - except: - galaxies_tags = [] - else: - galaxies_tags = [] - # custom tags - if tags: - tags = tags.split() - else: - tags = [] - tags = tags + taxonomies_tags + galaxies_tags - - # YARA # - if tracker_type == 'yara': - yara_default_rule = request.form.get("yara_default_rule") - yara_custom_rule = request.form.get("yara_custom_rule") - if yara_custom_rule: - to_track = yara_custom_rule - tracker_type = 'yara_custom' - else: - to_track = yara_default_rule - tracker_type = 'yara_default' - - if level == 'on': - level = 1 - else: - level = 0 - if mails: - mails = mails.split() - else: - tags = [] - - # FILTERS - filters = {} - for obj_type in Tracker.get_objects_tracked(): - new_filter = request.form.get(f'{obj_type}_obj') - if new_filter == 'on': - filters[obj_type] = {} - # Mimetypes - mimetypes = request.form.get(f'mimetypes_{obj_type}', []) - if mimetypes: - mimetypes = json.loads(mimetypes) - filters[obj_type]['mimetypes'] = mimetypes - # Sources - sources = request.form.get(f'sources_{obj_type}', []) - if sources: - sources = json.loads(sources) - filters[obj_type]['sources'] = sources - # Subtypes - for obj_subtype in ail_core.get_object_all_subtypes(obj_type): - subtype = request.form.get(f'filter_{obj_type}_{obj_subtype}') - if subtype == 'on': - if 'subtypes' not in filters[obj_type]: - filters[obj_type]['subtypes'] = [] - filters[obj_type]['subtypes'].append(obj_subtype) - - input_dict = {"tracked": to_track, "type": tracker_type, - "tags": tags, "mails": mails, "filters": filters, - "level": level, "description": description, "webhook": webhook} - if tracker_type == 'set': - try: - input_dict['nb_words'] = int(nb_words) - except TypeError: - input_dict['nb_words'] = 1 - + input_dict = parse_add_edit_request(request.form) user_id = current_user.get_id() res = Tracker.api_add_tracker(input_dict, user_id) if res[1] == 200: @@ -220,9 +291,42 @@ def add_tracked_menu(): return create_json_response(res[0], res[1]) else: return render_template("tracker_add.html", - all_sources=item_basic.get_all_items_sources(r_list=True), - tags_selector_data=Tag.get_tags_selector_data(), - all_yara_files=Tracker.get_all_default_yara_files()) + all_sources=item_basic.get_all_items_sources(r_list=True), + tags_selector_data=Tag.get_tags_selector_data(), + all_yara_files=Tracker.get_all_default_yara_files()) + +@hunters.route("/tracker/edit", methods=['GET', 'POST']) +@login_required +@login_analyst +def tracker_edit(): + if request.method == 'POST': + input_dict = parse_add_edit_request(request.form) + user_id = current_user.get_id() + res = Tracker.api_edit_tracker(input_dict, user_id) + if res[1] == 200: + return redirect(url_for('hunters.show_tracker', uuid=res[0].get('uuid'))) + else: + user_id = current_user.get_id() + tracker_uuid = request.args.get('uuid', None) + res = Tracker.api_is_allowed_to_edit_tracker(tracker_uuid, user_id) + if res[1] != 200: # invalid access + return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] + + tracker = Tracker.Tracker(tracker_uuid) + dict_tracker = tracker.get_meta(options={'description', 'level', 'mails', 'filters', 'tags', 'webhook'}) + if dict_tracker['type'] == 'yara': + if not Tracker.is_default_yara_rule(dict_tracker['tracked']): + dict_tracker['content'] = Tracker.get_yara_rule_content(dict_tracker['tracked']) + taxonomies_tags, galaxies_tags, custom_tags = Tag.sort_tags_taxonomies_galaxies_customs(dict_tracker['tags']) + tags_selector_data = Tag.get_tags_selector_data() + tags_selector_data['taxonomies_tags'] = taxonomies_tags + tags_selector_data['galaxies_tags'] = galaxies_tags + dict_tracker['tags'] = custom_tags + return render_template("tracker_add.html", + dict_tracker=dict_tracker, + all_sources=item_basic.get_all_items_sources(r_list=True), + tags_selector_data=tags_selector_data, + all_yara_files=Tracker.get_all_default_yara_files()) @hunters.route('/tracker/delete', methods=['GET']) @login_required @@ -234,7 +338,31 @@ def tracker_delete(): if res[1] != 200: return create_json_response(res[0], res[1]) else: - return redirect(url_for('hunter.tracked_menu')) + return redirect(url_for('hunters.trackers_dashboard')) + + +@hunters.route("/tracker/graph/json", methods=['GET']) +@login_required +@login_read_only +def get_json_tracker_graph(): + user_id = current_user.get_id() + tracker_uuid = request.args.get('uuid') + res = Tracker.api_check_tracker_acl(tracker_uuid, user_id) + if res: + return create_json_response(res[0], res[1]) + + date_from = request.args.get('date_from') + date_to = request.args.get('date_to') + + if date_from: + date_from = date_from.replace('-', '') + if date_to: + date_to = date_to.replace('-', '') + if date_from and date_to: + res = Tracker.get_trackers_graph_by_day([tracker_uuid], date_from=date_from, date_to=date_to) + else: + res = Tracker.get_trackers_graph_by_day([tracker_uuid]) + return jsonify(res) #################### diff --git a/var/www/modules/hunter/Flask_hunter.py b/var/www/modules/hunter/Flask_hunter.py deleted file mode 100644 index c71a91c9..00000000 --- a/var/www/modules/hunter/Flask_hunter.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env python3 -# -*-coding:UTF-8 -* - -''' - Flask functions and routes for tracked items -''' -import os -import sys -import json -import flask -from flask import Flask, render_template, jsonify, request, Blueprint, url_for, redirect, Response, escape - -from Role_Manager import login_admin, login_analyst, login_read_only -from flask_login import login_required, current_user - -sys.path.append(os.environ['AIL_BIN']) -################################## -# Import Project packages -################################## -from lib.objects import ail_objects -from lib import item_basic -from lib import Tracker -from lib import Tag -from packages import Date - - -# ============ VARIABLES ============ -import Flask_config - -app = Flask_config.app -baseUrl = Flask_config.baseUrl -bootstrap_label = Flask_config.bootstrap_label - -hunter = Blueprint('hunter', __name__, template_folder='templates') - -# ============ FUNCTIONS ============ - -def create_json_response(data, status_code): - return Response(json.dumps(data, indent=2, sort_keys=True), mimetype='application/json'), status_code - -# ============ ROUTES ============ - -@hunter.route("/tracker/edit", methods=['GET', 'POST']) -@login_required -@login_analyst -def edit_tracked_menu(): - user_id = current_user.get_id() - tracker_uuid = request.args.get('uuid', None) - - res = Tracker.api_is_allowed_to_edit_tracker(tracker_uuid, user_id) # check if is author or admin - if res[1] != 200: # invalid access - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - - tracker = Tracker.Tracker(tracker_uuid) - dict_tracker = tracker.get_meta(options={'description', 'level', 'mails', 'sources', 'tags', 'user', 'webhook'}) - dict_tracker['tags'] = ' '.join(dict_tracker['tags']) - dict_tracker['mails'] = ' '.join(dict_tracker['mails']) - - if dict_tracker['type'] == 'set': - dict_tracker['tracker'], dict_tracker['nb_words'] = dict_tracker['tracker'].split(';') - dict_tracker['tracker'] = dict_tracker['tracker'].replace(',', ' ') - elif dict_tracker['type'] == 'yara': #is_valid_default_yara_rule - if Tracker.is_default_yara_rule(dict_tracker['tracker']): - dict_tracker['yara_file'] = dict_tracker['tracker'].split('/') - dict_tracker['yara_file'] = dict_tracker['yara_file'][-2] + '/' + dict_tracker['yara_file'][-1] - dict_tracker['content'] = None - else: - dict_tracker['yara_file'] = None - dict_tracker['content'] = Tracker.get_yara_rule_content(dict_tracker['tracker']) - - return render_template("edit_tracker.html", dict_tracker=dict_tracker, - all_sources=item_basic.get_all_items_sources(r_list=True), - all_yara_files=Tracker.get_all_default_yara_files()) - - ## TO EDIT - # word - # set of word + nb words - # regex - # yara custom - # yara default ???? => allow edit ? - - #### EDIT SHow Trackers ?????????????????????????????????????????????????? - -@hunter.route("/tracker/show_tracker") -@login_required -@login_read_only -def show_tracker(): - user_id = current_user.get_id() - tracker_uuid = request.args.get('uuid', None) - res = Tracker.api_is_allowed_to_edit_tracker(tracker_uuid, user_id) - if res[1] != 200: # invalid access - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - - date_from = request.args.get('date_from') - date_to = request.args.get('date_to') - - if date_from: - date_from = date_from.replace('-', '') - if date_to: - date_to = date_to.replace('-', '') - - tracker = Tracker.Tracker(tracker_uuid) - meta = tracker.get_meta(options={'description', 'level', 'mails', 'filters', 'sparkline', 'tags', - 'user', 'webhook'}) - - if meta['type'] == 'yara': - yara_rule_content = Tracker.get_yara_rule_content(meta['tracked']) - else: - yara_rule_content = None - - if meta['type'] == 'typosquatting': - typo_squatting = Tracker.get_tracked_typosquatting_domains(meta['tracked']) - sorted(typo_squatting) - else: - typo_squatting = set() - - if date_from: - date_from, date_to = Date.sanitise_daterange(meta['first_seen'], meta['last_seen']) - objs = tracker.get_objs_by_daterange(date_from, date_to) - meta['objs'] = ail_objects.get_objects_meta(objs, flask_context=True) - else: - date_from = '' - date_to = '' - meta['objs'] = [] - - meta['date_from'] = date_from - meta['date_to'] = date_to - print(meta['filters']) - meta['item_sources'] = sorted(meta['filters'].get('item', {}).get('sources', [])) - # meta['filters'] = json.dumps(meta['filters'], indent=4) - - return render_template("showTracker.html", tracker_metadata=meta, - yara_rule_content=yara_rule_content, - typo_squatting=typo_squatting, - bootstrap_label=bootstrap_label) - -@hunter.route("/tracker/update_tracker_description", methods=['POST']) -@login_required -@login_analyst -def update_tracker_description(): - user_id = current_user.get_id() - term_uuid = request.form.get('uuid') - res = Tracker.api_is_allowed_to_edit_tracker(term_uuid, user_id) - if res[1] != 200: # invalid access - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - description = escape( str(request.form.get('description', '')) ) - Term.replace_tracker_description(term_uuid, description) - return redirect(url_for('hunter.show_tracker', uuid=term_uuid)) - -@hunter.route("/tracker/update_tracker_tags", methods=['POST']) -@login_required -@login_analyst -def update_tracker_tags(): - user_id = current_user.get_id() - term_uuid = request.form.get('uuid') - res = Tracker.api_is_allowed_to_edit_tracker(term_uuid, user_id) - if res[1] != 200: # invalid access - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - tags = request.form.get('tags') - if tags: - tags = tags.split() - else: - tags = [] - Term.replace_tracked_term_tags(term_uuid, tags) - return redirect(url_for('hunter.show_tracker', uuid=term_uuid)) - -@hunter.route("/tracker/update_tracker_mails", methods=['POST']) -@login_required -@login_analyst -def update_tracker_mails(): - user_id = current_user.get_id() - term_uuid = request.form.get('uuid') - res = Tracker.api_is_allowed_to_edit_tracker(term_uuid, user_id) - if res[1] != 200: # invalid access - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - mails = request.form.get('mails') - if mails: - mails = mails.split() - else: - mails = [] - res = Term.replace_tracked_term_mails(term_uuid, mails) - if res: # invalid mail - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - return redirect(url_for('hunter.show_tracker', uuid=term_uuid)) - -@hunter.route("/tracker/get_json_tracker_stats", methods=['GET']) -@login_required -@login_read_only -def get_json_tracker_stats(): - user_id = current_user.get_id() - tracker_uuid = request.args.get('uuid') - res = Tracker.api_check_tracker_acl(tracker_uuid, user_id) - if res: - return create_json_response(res[0], res[1]) - - date_from = request.args.get('date_from') - date_to = request.args.get('date_to') - - if date_from: - date_from = date_from.replace('-', '') - if date_to: - date_to = date_to.replace('-', '') - if date_from and date_to: - res = Tracker.get_trackers_graph_by_day([tracker_uuid], date_from=date_from, date_to=date_to) - else: - res = Tracker.get_trackers_graph_by_day([tracker_uuid]) - return jsonify(res) - -@hunter.route("/tracker/yara/default_rule/content", methods=['GET']) -@login_required -@login_read_only -def get_default_yara_rule_content(): - default_yara_rule = request.args.get('rule_name') - res = Tracker.api_get_default_rule_content(default_yara_rule) - return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] - - -# ========= REGISTRATION ========= -app.register_blueprint(hunter, url_prefix=baseUrl) diff --git a/var/www/modules/hunter/templates/showTracker.html b/var/www/modules/hunter/templates/showTracker.html deleted file mode 100644 index 466f91f5..00000000 --- a/var/www/modules/hunter/templates/showTracker.html +++ /dev/null @@ -1,440 +0,0 @@ - - - -
- - -Type | -Tracker | -Created | -Access Level | -Created by | -First seen | -Last seen | - {% if tracker_metadata['webhook'] %} -Webhook | - {% endif %} -Tags | -||
---|---|---|---|---|---|---|---|---|---|---|
{{ tracker_metadata['type'] }} | - {% if tracker_metadata['type'] == 'typosquatting' %} -
-
- {{ tracker_metadata['tracked'].split(",")[0] }}
-
-
-
-
- {% if typo_squatting %}
- {% for typo in typo_squatting %}
- {{typo}}
-
- - {% endfor %} - {%endif%} - |
- {% else %}
- {{ tracker_metadata['tracked'] }} | - {% endif %} -{{ tracker_metadata['date'][0:4] }}/{{ tracker_metadata['date'][4:6] }}/{{ tracker_metadata['date'][6:8] }} | -- {% if tracker_metadata['level'] == 0 %} - Private - {% else %} - Global - {% endif %} - | -{{ tracker_metadata['user'] }} | -- {% if tracker_metadata['first_seen'] %} - {{ tracker_metadata['first_seen'][0:4] }}/{{ tracker_metadata['first_seen'][4:6] }}/{{ tracker_metadata['first_seen'][6:8] }} - {% endif %} - | -- {% if tracker_metadata['last_seen'] %} - {{ tracker_metadata['last_seen'][0:4] }}/{{ tracker_metadata['last_seen'][4:6] }}/{{ tracker_metadata['last_seen'][6:8] }} - {% endif %} - | - {% if tracker_metadata['webhook'] %} -- Turned ON - | - {% endif %} -- {% for tag in tracker_metadata['tags'] %} - - {{ tag }} - - {% endfor %} - - | -
- {% for mail in tracker_metadata['mails'] %}
- {{ mail }} - {% endfor %} - |
-
{{ tracker_metadata['filters'] }}- {% else %} - No Filters
-
{{ yara_rule_content }}- {% endif %} - -
Type | -- | Id | -Tags | -- |
---|---|---|---|---|
- {% with style=object['icon']['style'], icon=object['icon']['icon'] , color=object['icon']['color'] %} - {% include 'objects/obj_svg_block.html' %} - {% endwith %} - {{ object['type']}} - | -- {% if object['subtype'] %} - {{ object['subtype']}} - {% endif %} - | -- - {{ object['id']}} - - | -- {% for tag in object['tags'] %} - {{ tag }} - {% endfor %} - | --{# #} -{# #} -{# #} - | -
Type | ++ {% if meta['type'] == 'word' %} + + {% elif meta['type'] == 'set' %} + + {% elif meta['type'] == 'regex' %} + + {% elif meta['type'] == 'typosquatting' %} + + {% elif meta['type'] == 'yara' %} + { + {% endif %} + {{ meta['type'] }} + | +
Tracked | +
+ {% if meta['type'] == 'typosquatting' %}
+
+ {{ meta['tracked'] }}
+
+
+
+ {% else %}
+ {{ meta['tracked'] }}
+ {% endif %}
+
+ {% if typo_squatting %}
+ {% for typo in typo_squatting %}
+ {{typo}}
+
+ + {% endfor %} + {% endif %} + |
+
Date | ++ {{meta['date'][0:4]}}/{{meta['date'][4:6]}}/{{meta['date'][6:8]}} + | +
Level | ++ {% if meta['level'] == 0 %} + Private + {% else %} + Global + {% endif %} + | +
Creator | +{{meta['user']}} | +
First Seen | ++ {% if meta['first_seen'] %} + {{ meta['first_seen'][0:4] }} / {{ meta['first_seen'][4:6] }} / {{ meta['first_seen'][6:8] }} + {% endif %} + | +
Last Seen | ++ {% if meta['last_seen'] %} + {{ meta['last_seen'][0:4] }} / {{ meta['last_seen'][4:6] }} / {{ meta['last_seen'][6:8] }} + {% endif %} + | +
Tags | ++ {%for tag in meta['tags']%} + {{ tag }} + {%endfor%} + | +
Mails | +
+ {% for mail in meta['mails'] %}
+ {{ mail }}
+ {% endfor %}
+ |
+
Webhook | +{{meta['webhook']}} | +
Filters | +
+
+ {% if meta['filters'] %}
+
+ {{ meta['filters'] }}+ {% else %} + No Filters + {% endif %} + |
+
Objects Match | +
+ {%for obj_type in meta['nb_objs']%}
+ + {{ obj_type }} + {{ meta['nb_objs'][obj_type] }} ++ {%endfor%} + |
+
+
{{ rule_content }}+ + {% endif %} + +
Type | ++ | Id | +Tags | ++ |
---|---|---|---|---|
+ {% with style=object['icon']['style'], icon=object['icon']['icon'] , color=object['icon']['color'] %} + {% include 'objects/obj_svg_block.html' %} + {% endwith %} + {{ object['type']}} + | ++ {% if object['subtype'] %} + {{ object['subtype']}} + {% endif %} + | ++ + {{ object['id']}} + + | ++ {% for tag in object['tags'] %} + {{ tag }} + {% endfor %} + | ++{# #} +{# #} +{# #} + | +