2020-07-10 15:54:14 +02:00
#!/usr/bin/env python3
# -*-coding:UTF-8 -*
import os
2020-12-08 16:47:55 +01:00
import re
2020-07-10 15:54:14 +02:00
import sys
import time
import redis
2020-12-08 16:47:55 +01:00
import uuid
2020-08-12 09:28:36 +02:00
import yara
2020-12-08 16:47:55 +01:00
import datetime
from flask import escape
2020-07-10 15:54:14 +02:00
sys . path . append ( os . path . join ( os . environ [ ' AIL_BIN ' ] , ' lib/ ' ) )
import ConfigLoader
2020-07-10 15:57:29 +02:00
#import item_basic
2020-07-10 15:54:14 +02:00
config_loader = ConfigLoader . ConfigLoader ( )
2020-12-08 16:47:55 +01:00
r_serv_db = config_loader . get_redis_conn ( " ARDB_DB " )
2020-07-10 15:54:14 +02:00
r_serv_tracker = config_loader . get_redis_conn ( " ARDB_Tracker " )
config_loader = None
2020-12-08 16:47:55 +01:00
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 ' )
2020-08-12 09:28:36 +02:00
def get_tracker_uuid_list ( tracker , tracker_type ) :
return list ( r_serv_tracker . smembers ( ' all:tracker_uuid: {} : {} ' . format ( tracker_type , tracker ) ) )
def get_tracker_tags ( tracker_uuid ) :
return list ( r_serv_tracker . smembers ( ' tracker:tags: {} ' . format ( tracker_uuid ) ) )
def get_tracker_mails ( tracker_uuid ) :
return list ( r_serv_tracker . smembers ( ' tracker:mail: {} ' . format ( tracker_uuid ) ) )
2020-07-10 15:57:29 +02:00
def get_tracker_description ( tracker_uuid ) :
2020-07-10 16:03:51 +02:00
return r_serv_tracker . hget ( ' tracker: {} ' . format ( tracker_uuid ) , ' description ' )
2020-07-10 15:54:14 +02:00
2020-12-08 16:47:55 +01:00
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
2020-08-12 09:28:36 +02:00
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 )
# track nb item by date
r_serv_tracker . zadd ( ' tracker:stat: {} ' . format ( tracker_uuid ) , item_date , int ( item_date ) )
2020-07-10 15:54:14 +02:00
def get_email_subject ( tracker_uuid ) :
tracker_description = get_tracker_description ( tracker_uuid )
if not tracker_description :
return " AIL framework: Tracker Alert "
else :
return ' AIL framework: {} ' . format ( tracker_description )
2020-08-12 09:28:36 +02:00
def get_tracker_last_updated_by_type ( tracker_type ) :
2020-08-18 21:42:30 +02:00
epoch_update = r_serv_tracker . get ( ' tracker:refresh: {} ' . format ( tracker_type ) )
2020-08-12 09:28:36 +02:00
if not epoch_update :
epoch_update = 0
return float ( epoch_update )
2020-12-08 16:47:55 +01:00
######################
#### 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 )
2020-12-08 17:08:39 +01:00
if not is_in_role ( user_id , ' admin ' ) and user_id != tracker_creator :
2020-12-08 16:47:55 +01:00
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 --##
##############
2020-08-12 09:28:36 +02:00
#### YARA ####
def get_yara_rules_dir ( ) :
return os . path . join ( os . environ [ ' AIL_BIN ' ] , ' trackers ' , ' yara ' )
def get_yara_rules_default_dir ( ) :
return os . path . join ( os . environ [ ' AIL_BIN ' ] , ' trackers ' , ' yara ' , ' ail-yara-rules ' , ' rules ' )
# # TODO: cache + update
def get_all_default_yara_rules_types ( ) :
yara_dir = get_yara_rules_default_dir ( )
all_yara_types = next ( os . walk ( yara_dir ) ) [ 1 ]
# save in cache ?
return all_yara_types
# # TODO: cache + update
def get_all_default_yara_files ( ) :
yara_dir = get_yara_rules_default_dir ( )
all_default_yara_files = { }
for rules_type in get_all_default_yara_rules_types ( ) :
all_default_yara_files [ rules_type ] = os . listdir ( os . path . join ( yara_dir , rules_type ) )
return all_default_yara_files
def get_all_default_yara_rules_by_type ( yara_types ) :
all_default_yara_files = get_all_default_yara_files ( )
if yara_types in all_default_yara_files :
return all_default_yara_files [ yara_types ]
else :
return [ ]
def get_all_tracked_yara_files ( ) :
yara_files = r_serv_tracker . smembers ( ' all:tracker:yara ' )
if not yara_files :
yara_files = [ ]
return yara_files
def reload_yara_rules ( ) :
yara_files = get_all_tracked_yara_files ( )
# {uuid: filename}
rule_dict = { }
for yar_path in yara_files :
l_tracker_uuid = get_tracker_uuid_list ( yar_path , ' yara ' )
for tracker_uuid in l_tracker_uuid :
rule_dict [ tracker_uuid ] = os . path . join ( get_yara_rules_dir ( ) , yar_path )
rules = yara . compile ( filepaths = rule_dict )
return rules
def is_valid_yara_rule ( yara_rule ) :
try :
yara . compile ( source = yara_rule )
return True
except :
return False
2020-12-08 16:47:55 +01:00
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 ) :
2020-08-12 09:28:36 +02:00
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 :
2020-12-08 16:47:55 +01:00
if verbose :
print ( ' error: file transversal ' )
print ( yara_dir )
print ( filename )
2020-08-12 09:28:36 +02:00
return False
else :
if os . path . isfile ( filename ) :
return True
else :
return False
def save_yara_rule ( yara_rule_type , yara_rule , tracker_uuid = None ) :
if yara_rule_type == ' yara_custom ' :
if not tracker_uuid :
tracker_uuid = str ( uuid . uuid4 ( ) )
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 ' :
filename = os . path . join ( ' ail-yara-rules ' , ' rules ' , yara_rule )
return filename
2020-08-19 11:37:51 +02:00
2020-12-08 16:47:55 +01:00
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
2020-08-19 11:37:51 +02:00
def get_yara_rule_content ( yara_rule ) :
yara_dir = get_yara_rules_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 :
return ' ' # # TODO: throw exception
with open ( filename , ' r ' ) as f :
rule_content = f . read ( )
return rule_content
2020-09-03 16:33:10 +02:00
def api_get_default_rule_content ( default_yara_rule ) :
yara_dir = get_yara_rules_default_dir ( )
filename = os . path . join ( yara_dir , default_yara_rule )
filename = os . path . realpath ( filename )
# incorrect filename
if not os . path . commonprefix ( [ filename , yara_dir ] ) == yara_dir :
return ( { ' status ' : ' error ' , ' reason ' : ' file transversal detected ' } , 400 )
if not os . path . isfile ( filename ) :
return ( { ' status ' : ' error ' , ' reason ' : ' yara rule not found ' } , 400 )
with open ( filename , ' r ' ) as f :
rule_content = f . read ( )
return ( { ' rule_name ' : default_yara_rule , ' content ' : rule_content } , 200 )
2020-08-12 09:28:36 +02:00
##-- YARA --##
if __name__ == ' __main__ ' :
res = is_valid_yara_rule ( ' rule dummy { } ' )
print ( res )