diff --git a/bin/lib/Tracker.py b/bin/lib/Tracker.py index ff646bfe..9efbd375 100755 --- a/bin/lib/Tracker.py +++ b/bin/lib/Tracker.py @@ -2,19 +2,78 @@ # -*-coding:UTF-8 -* import os +import re import sys import time import redis +import uuid import yara +import datetime + +from flask import escape sys.path.append(os.path.join(os.environ['AIL_BIN'], 'lib/')) import ConfigLoader #import item_basic config_loader = ConfigLoader.ConfigLoader() +r_serv_db = config_loader.get_redis_conn("ARDB_DB") r_serv_tracker = config_loader.get_redis_conn("ARDB_Tracker") config_loader = None +email_regex = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}' +email_regex = re.compile(email_regex) + +special_characters = set('[<>~!?@#$%^&*|()_-+={}":;,.\'\n\r\t]/\\') +special_characters.add('\\s') + +############### +#### UTILS #### +def is_valid_uuid_v4(UUID): + if not UUID: + return False + UUID = UUID.replace('-', '') + try: + uuid_test = uuid.UUID(hex=UUID, version=4) + return uuid_test.hex == UUID + except: + return False + +def is_valid_regex(tracker_regex): + try: + re.compile(tracker_regex) + return True + except: + return False + +def is_valid_mail(email): + result = email_regex.match(email) + if result: + return True + else: + return False + +def verify_mail_list(mail_list): + for mail in mail_list: + if not is_valid_mail(mail): + return ({'status': 'error', 'reason': 'Invalid email', 'value': mail}, 400) + return None + +##-- UTILS --## +############### + +def get_tracker_by_uuid(tracker_uuid): + return r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'tracked') + +def get_tracker_type(tracker_uuid): + return r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'type') + +def get_tracker_level(tracker_uuid): + return int(r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'level')) + +def get_tracker_user_id(tracker_uuid): + return r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'user_id') + def get_tracker_uuid_list(tracker, tracker_type): return list(r_serv_tracker.smembers('all:tracker_uuid:{}:{}'.format(tracker_type, tracker))) @@ -27,6 +86,51 @@ def get_tracker_mails(tracker_uuid): def get_tracker_description(tracker_uuid): return r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'description') +def get_tracker_first_seen(tracker_uuid): + res = r_serv_tracker.zrange('tracker:stat:{}'.format(tracker_uuid), 0, 0) + if res: + return res[0] + else: + return None + +def get_tracker_last_seen(tracker_uuid): + res = r_serv_tracker.zrevrange('tracker:stat:{}'.format(tracker_uuid), 0, 0) + if res: + return res[0] + else: + return None + +def get_tracker_metedata(tracker_uuid, user_id=False, description=False, level=False, tags=False, mails=False, sparkline=False): + dict_uuid = {} + dict_uuid['tracker'] = get_tracker_by_uuid(tracker_uuid) + dict_uuid['type'] = get_tracker_type(tracker_uuid) + dict_uuid['date'] = r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'date') + dict_uuid['description'] = get_tracker_description(tracker_uuid) + dict_uuid['first_seen'] = get_tracker_first_seen(tracker_uuid) + dict_uuid['last_seen'] = get_tracker_last_seen(tracker_uuid) + if user_id: + dict_uuid['user_id'] = get_tracker_user_id(tracker_uuid) + if level: + dict_uuid['level'] = get_tracker_level(tracker_uuid) + if mails: + dict_uuid['mails'] = get_tracker_mails(tracker_uuid) + if tags: + dict_uuid['tags'] = get_tracker_tags(tracker_uuid) + if sparkline: + dict_uuid['sparkline'] = get_tracker_sparkline(tracker_uuid) + dict_uuid['uuid'] = tracker_uuid + return dict_uuid + +def get_tracker_sparkline(tracker_uuid, num_day=6): + date_range_sparkline = Date.get_date_range(num_day) + sparklines_value = [] + for date_day in date_range_sparkline: + nb_seen_this_day = r_serv_tracker.scard('tracker:item:{}:{}'.format(tracker_uuid, date_day)) + if nb_seen_this_day is None: + nb_seen_this_day = 0 + sparklines_value.append(int(nb_seen_this_day)) + return sparklines_value + def add_tracked_item(tracker_uuid, item_id, item_date): # track item r_serv_tracker.sadd('tracker:item:{}:{}'.format(tracker_uuid, item_date), item_id) @@ -46,6 +150,234 @@ def get_tracker_last_updated_by_type(tracker_type): epoch_update = 0 return float(epoch_update) +###################### +#### TRACKERS ACL #### + +# # TODO: use new package => duplicate fct +def is_in_role(user_id, role): + if r_serv_db.sismember('user_role:{}'.format(role), user_id): + return True + else: + return False + +def is_tracker_in_global_level(tracker, tracker_type): + res = r_serv_tracker.smembers('all:tracker_uuid:{}:{}'.format(tracker_type, tracker)) + if res: + for elem_uuid in res: + if r_serv_tracker.hget('tracker:{}'.format(elem_uuid), 'level')=='1': + return True + return False + +def is_tracker_in_user_level(tracker, tracker_type, user_id): + res = r_serv_tracker.smembers('user:tracker:{}'.format(user_id)) + if res: + for elem_uuid in res: + if r_serv_tracker.hget('tracker:{}'.format(elem_uuid), 'tracked')== tracker: + if r_serv_tracker.hget('tracker:{}'.format(elem_uuid), 'type')== tracker_type: + return True + return False + +def api_is_allowed_to_edit_tracker(tracker_uuid, user_id): + if not is_valid_uuid_v4(tracker_uuid): + return ({"status": "error", "reason": "Invalid uuid"}, 400) + tracker_creator = r_serv_tracker.hget('tracker:{}'.format(tracker_uuid), 'user_id') + if not tracker_creator: + return ({"status": "error", "reason": "Unknown uuid"}, 404) + if not is_in_role(user_id, 'admin') or user_id != tracker_creator: + return ({"status": "error", "reason": "Access Denied"}, 403) + return ({"uuid": tracker_uuid}, 200) + + +##-- ACL --## + +#### CREATE TRACKER #### +def api_validate_tracker_to_add(tracker , tracker_type, nb_words=1): + if tracker_type=='regex': + if not is_valid_regex(tracker): + return ({"status": "error", "reason": "Invalid regex"}, 400) + elif tracker_type=='word' or tracker_type=='set': + # force lowercase + tracker = tracker.lower() + word_set = set(tracker) + set_inter = word_set.intersection(special_characters) + if set_inter: + return ({"status": "error", "reason": f'special character(s) not allowed: {set_inter}', "message": "Please use a python regex or remove all special characters"}, 400) + words = tracker.split() + # not a word + if tracker_type=='word' and len(words)>1: + tracker_type = 'set' + + # ouput format: tracker1,tracker2,tracker3;2 + if tracker_type=='set': + try: + nb_words = int(nb_words) + except: + nb_words = 1 + if nb_words==0: + nb_words = 1 + + words_set = set(words) + words_set = sorted(words_set) + + if nb_words > len(words_set): + nb_words = len(words_set) + + tracker = ",".join(words_set) + tracker = "{};{}".format(tracker, nb_words) + + elif tracker_type=='yara_custom': + if not is_valid_yara_rule(tracker): + return ({"status": "error", "reason": "Invalid custom Yara Rule"}, 400) + elif tracker_type=='yara_default': + if not is_valid_default_yara_rule(tracker): + return ({"status": "error", "reason": "The Yara Rule doesn't exist"}, 400) + else: + return ({"status": "error", "reason": "Incorrect type"}, 400) + return ({"status": "success", "tracker": tracker, "type": tracker_type}, 200) + +def create_tracker(tracker, tracker_type, user_id, level, tags, mails, description, dashboard=0, tracker_uuid=None): + # edit tracker + if tracker_uuid: + edit_tracker = True + # check if type changed + old_type = get_tracker_type(tracker_uuid) + old_tracker = get_tracker_by_uuid(tracker_uuid) + old_level = get_tracker_level(tracker_uuid) + tracker_user_id = get_tracker_user_id(tracker_uuid) + + # Create new tracker + else: + edit_tracker = False + # generate tracker uuid + tracker_uuid = str(uuid.uuid4()) + old_type = None + old_tracker = None + + # YARA + if tracker_type == 'yara_custom' or tracker_type == 'yara_default': + # delete yara rule + if tracker_type == 'yara_default' and old_type == 'yara': + if not is_default_yara_rule(old_tracker): + filepath = get_yara_rule_file_by_tracker_name(old_tracker) + if filepath: + os.remove(filepath) + tracker = save_yara_rule(tracker_type, tracker, tracker_uuid=tracker_uuid) + tracker_type = 'yara' + + # create metadata + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'tracked', tracker) + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'type', tracker_type) + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'date', datetime.date.today().strftime("%Y%m%d")) + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'level', level) + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'dashboard', dashboard) + if not edit_tracker: + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'user_id', user_id) + + if description: + r_serv_tracker.hset('tracker:{}'.format(tracker_uuid), 'description', description) + + # type change + if edit_tracker: + r_serv_tracker.srem('all:tracker:{}'.format(old_type), old_tracker) + r_serv_tracker.srem('all:tracker_uuid:{}:{}'.format(old_type, old_tracker), tracker_uuid) + if level != old_level: + if level == 0: + r_serv_tracker.srem('global:tracker', tracker_uuid) + elif level == 1: + r_serv_tracker.srem('user:tracker:{}'.format(tracker_user_id), tracker_uuid) + if tracker_type != old_type: + if old_level == 0: + r_serv_tracker.srem('user:tracker:{}:{}'.format(tracker_user_id, old_type), tracker_uuid) + elif old_level == 1: + r_serv_tracker.srem('global:tracker:{}'.format(old_type), tracker_uuid) + if old_type=='yara': + if not is_default_yara_rule(old_tracker): + filepath = get_yara_rule_file_by_tracker_name(old_tracker) + if filepath: + os.remove(filepath) + + # create all tracker set + r_serv_tracker.sadd('all:tracker:{}'.format(tracker_type), tracker) + + # create tracker - uuid map + r_serv_tracker.sadd('all:tracker_uuid:{}:{}'.format(tracker_type, tracker), tracker_uuid) + + # add display level set + if level == 0: # user only + r_serv_tracker.sadd('user:tracker:{}'.format(user_id), tracker_uuid) + r_serv_tracker.sadd('user:tracker:{}:{}'.format(user_id, tracker_type), tracker_uuid) + elif level == 1: # global + r_serv_tracker.sadd('global:tracker', tracker_uuid) + r_serv_tracker.sadd('global:tracker:{}'.format(tracker_type), tracker_uuid) + + # create tracker tags list + for tag in tags: + r_serv_tracker.sadd('tracker:tags:{}'.format(tracker_uuid), escape(tag) ) + + # create tracker tags mail notification list + for mail in mails: + r_serv_tracker.sadd('tracker:mail:{}'.format(tracker_uuid), escape(mail) ) + + # toggle refresh module tracker list/set + r_serv_tracker.set('tracker:refresh:{}'.format(tracker_type), time.time()) + if tracker_type != old_type: # toggle old type refresh + r_serv_tracker.set('tracker:refresh:{}'.format(old_type), time.time()) + return tracker_uuid + +def api_add_tracker(dict_input, user_id): + tracker = dict_input.get('tracker', None) + if not tracker: + 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) + + res = api_validate_tracker_to_add(tracker , tracker_type, nb_words=nb_words) + if res[1]!=200: + return res + tracker = res[0]['tracker'] + tracker_type = res[0]['type'] + + tags = dict_input.get('tags', []) + mails = dict_input.get('mails', []) + res = verify_mail_list(mails) + if res: + return res + + ## TODO: add dashboard key + level = dict_input.get('level', 1) + try: + level = int(level) + if level not in range(0, 1): + level = 1 + except: + level = 1 + + 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_tracker_in_global_level(tracker, tracker_type) and not tracker_uuid: + return ({"status": "error", "reason": "Tracker already exist"}, 409) + else: + if is_tracker_in_user_level(tracker, tracker_type, user_id) and not tracker_uuid: + return ({"status": "error", "reason": "Tracker already exist"}, 409) + + tracker_uuid = create_tracker(tracker , tracker_type, user_id, level, tags, mails, description, tracker_uuid=tracker_uuid) + + return ({'tracker': tracker, 'type': tracker_type, 'uuid': tracker_uuid}, 200) + +##-- CREATE TRACKER --## + +############## #### YARA #### def get_yara_rules_dir(): return os.path.join(os.environ['AIL_BIN'], 'trackers', 'yara') @@ -99,15 +431,32 @@ def is_valid_yara_rule(yara_rule): except: return False -def is_valid_default_yara_rule(yara_rule): +def is_default_yara_rule(tracked_yara_name): + yara_dir = get_yara_rules_dir() + filename = os.path.join(yara_dir, tracked_yara_name) + filename = os.path.realpath(filename) + try: + if tracked_yara_name.split('/')[0] == 'custom-rules': + return False + except: + return False + if not os.path.commonprefix([filename, yara_dir]) == yara_dir: + return False + else: + if os.path.isfile(filename): + return True + return False + +def is_valid_default_yara_rule(yara_rule, verbose=True): yara_dir = get_yara_rules_default_dir() filename = os.path.join(yara_dir, yara_rule) filename = os.path.realpath(filename) # incorrect filename if not os.path.commonprefix([filename, yara_dir]) == yara_dir: - print('error: file transversal') - print(yara_dir) - print(filename) + if verbose: + print('error: file transversal') + print(yara_dir) + print(filename) return False else: if os.path.isfile(filename): @@ -126,6 +475,17 @@ def save_yara_rule(yara_rule_type, yara_rule, tracker_uuid=None): filename = os.path.join('ail-yara-rules', 'rules', yara_rule) return filename +def get_yara_rule_file_by_tracker_name(tracked_yara_name): + yara_dir = get_yara_rules_dir() + filename = os.path.join(yara_dir, tracked_yara_name) + filename = os.path.realpath(filename) + if not os.path.commonprefix([filename, yara_dir]) == yara_dir: + print('error: file transversal') + print(yara_dir) + print(filename) + return None + return filename + def get_yara_rule_content(yara_rule): yara_dir = get_yara_rules_dir() filename = os.path.join(yara_dir, yara_rule) @@ -157,7 +517,6 @@ def api_get_default_rule_content(default_yara_rule): ##-- YARA --## - if __name__ == '__main__': res = is_valid_yara_rule('rule dummy { }') print(res) diff --git a/bin/packages/Term.py b/bin/packages/Term.py index 7896dbbe..773310c9 100755 --- a/bin/packages/Term.py +++ b/bin/packages/Term.py @@ -38,6 +38,8 @@ tokenizer = RegexpTokenizer('[\&\~\:\;\,\.\(\)\{\}\|\[\]\\\\/\-/\=\'\"\%\$\?\@\+ gaps=True, discard_empty=True) def is_valid_uuid_v4(UUID): + if not UUID: + return False UUID = UUID.replace('-', '') try: uuid_test = uuid.UUID(hex=UUID, version=4) @@ -215,11 +217,12 @@ def parse_tracked_term_to_add(term , term_type, nb_words=1): words_set = set(words) words_set = sorted(words_set) + if nb_words > len(words_set): + nb_words = len(words_set) + term = ",".join(words_set) term = "{};{}".format(term, nb_words) - if nb_words > len(words_set): - nb_words = len(words_set) elif term_type=='yara_custom': if not Tracker.is_valid_yara_rule(term): return ({"status": "error", "reason": "Invalid custom Yara Rule"}, 400) @@ -322,8 +325,11 @@ def delete_term(term_uuid): r_serv_term.delete('tracker:stat:{}'.format(term_uuid)) if term_type == 'yara': - # # TODO: - pass + # delete custom rule + if not Tracker.is_default_yara_rule(term): + filepath = Tracker.get_yara_rule_file_by_tracker_name(term) + if filepath: + os.remove(filepath) def replace_tracker_description(term_uuid, description): description = escape(description) diff --git a/var/www/modules/hunter/Flask_hunter.py b/var/www/modules/hunter/Flask_hunter.py index a1281d4c..1ada2914 100644 --- a/var/www/modules/hunter/Flask_hunter.py +++ b/var/www/modules/hunter/Flask_hunter.py @@ -93,8 +93,9 @@ def tracked_menu_yara(): @login_analyst def add_tracked_menu(): if request.method == 'POST': - term = request.form.get("term") - term_type = request.form.get("tracker_type") + tracker = 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", '') level = request.form.get("level", 0) @@ -102,15 +103,15 @@ def add_tracked_menu(): mails = request.form.get("mails", []) # YARA # - if term_type == '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: - term = yara_custom_rule - term_type='yara_custom' + tracker = yara_custom_rule + tracker_type='yara_custom' else: - term = yara_default_rule - term_type='yara_default' + tracker = yara_default_rule + tracker_type='yara_default' # # if level == 'on': @@ -121,17 +122,58 @@ def add_tracked_menu(): if tags: tags = tags.split() - input_dict = {"term": term, "type": term_type, "nb_words": nb_words, "tags": tags, "mails": mails, "level": level, "description": description} + input_dict = {"tracker": tracker, "type": tracker_type, "nb_words": nb_words, "tags": tags, "mails": mails, "level": level, "description": description} user_id = current_user.get_id() - res = Term.parse_json_term_to_add(input_dict, user_id) + # edit tracker + if tracker_uuid: + input_dict['uuid'] = tracker_uuid + res = Tracker.api_add_tracker(input_dict, user_id) if res[1] == 200: return redirect(url_for('hunter.tracked_menu')) else: ## TODO: use modal return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] else: - all_yara_files = Tracker.get_all_default_yara_files() - return render_template("Add_tracker.html", all_yara_files=all_yara_files) + return render_template("edit_tracker.html", all_yara_files=Tracker.get_all_default_yara_files()) + +@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 = Term.check_term_uuid_valid_access(tracker_uuid, user_id) # check if is author or admin + if res: # invalid access + return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] + + dict_tracker = Tracker.get_tracker_metedata(tracker_uuid, user_id=True, level=True, description=True, tags=True, mails=True) + 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_yara_files=Tracker.get_all_default_yara_files()) + + ## TO EDIT + # word + # set of word + nb words + # regex + # yara custum + # yara default ???? => allow edit ? + + #### EDIT SHow Trackers ?????????????????????????????????????????????????? @hunter.route("/tracker/show_tracker") @login_required diff --git a/var/www/modules/hunter/templates/Add_tracker.html b/var/www/modules/hunter/templates/edit_tracker.html similarity index 69% rename from var/www/modules/hunter/templates/Add_tracker.html rename to var/www/modules/hunter/templates/edit_tracker.html index 091d66f5..54eb5bcc 100644 --- a/var/www/modules/hunter/templates/Add_tracker.html +++ b/var/www/modules/hunter/templates/edit_tracker.html @@ -27,39 +27,41 @@
-
-
-
Create a new tracker
+
+
+
Edit a Tracker
-

Select a tracker type.

+ {%if dict_tracker%} + + {%endif%}
-
+
- +
-
+
- +
-
+
- +
- + @@ -68,6 +70,7 @@

+

Tracker Type:

+
- +
@@ -92,11 +95,12 @@
- {% for yara_types in all_yara_files %} - {% for yara_file in all_yara_files[yara_types] %} - + {% for yara_file_name in all_yara_files[yara_types] %} + {% endfor %} {% endfor %} @@ -107,14 +111,15 @@
+
Custom YARA rules:
- +

@@ -139,7 +144,7 @@ $(document).ready(function(){ $("#page-Tracker").addClass("active"); $("#nav_manual_crawler").addClass("active"); $("#tracker_desc").hide(); - $("#term").hide(); + $("#tracker").hide(); $("#nb_word").hide(); $("#yara_rule").hide(); @@ -148,30 +153,38 @@ $(document).ready(function(){ if (tracker_type=="word") { $("#tracker_desc").text("Token to track. You need to use a regex if you want to use one of the following special characters [<>~!?@#$%^&*|()_-+={}\":;,.\'\n\r\t]/\\ "); $("#tracker_desc").show(); - $("#term").show(); + $("#tracker").show(); $("#nb_word").hide(); $("#yara_rule").hide(); } else if (tracker_type=="set") { $("#tracker_desc").text("Set of Terms to track (space separated). This tracker is used to check if an item contain one or more terms specified in a set. If an item contain NB unique terms (by default NB of unique keywords = 1), this tracker is triggered. You need to use a regex if you want to use one of the following special characters [<>~!?@#$%^&*|()_-+={}\":;,.\'\n\r\t]/\\ "); $("#tracker_desc").show(); - $("#term").show(); + $("#tracker").show(); $("#nb_word").show(); $("#yara_rule").hide(); } else if (tracker_type=="regex") { $("#tracker_desc").text("Enter a valid Python regex"); $("#tracker_desc").show(); - $("#term").show(); + $("#tracker").show(); $("#nb_word").hide(); $("#yara_rule").hide(); } else if (tracker_type=="yara") { $("#tracker_desc").text("Select a default yara rule or create your own rule:"); $("#tracker_desc").show(); - $("#term").hide(); + $("#tracker").hide(); $("#nb_word").hide(); $("#yara_rule").show(); } }); + {%if dict_tracker%} + $('#tracker_type').val('{{dict_tracker['type']}}').change(); + + {%if dict_tracker['type']=='yara' and dict_tracker['yara_file']%} + $('#yara_default_rule').val('{{dict_tracker['yara_file']}}').change(); + {%endif%} + {%endif%} + }); function toggle_sidebar(){ diff --git a/var/www/modules/hunter/templates/showTracker.html b/var/www/modules/hunter/templates/showTracker.html index c0d0d589..af7d2b50 100644 --- a/var/www/modules/hunter/templates/showTracker.html +++ b/var/www/modules/hunter/templates/showTracker.html @@ -171,10 +171,14 @@
- - - + {%if yara_rule_content%}



{{ yara_rule_content }}