2018-05-04 11:53:29 +00:00
#!/usr/bin/env python3
2014-08-06 09:43:40 +00:00
# -*-coding:UTF-8 -*
2019-06-19 13:00:25 +00:00
import os
import re
import sys
2019-06-24 11:43:16 +00:00
import ssl
2019-07-31 11:24:43 +00:00
import json
2019-06-24 11:43:16 +00:00
import time
2019-06-19 13:00:25 +00:00
2014-08-14 15:55:18 +00:00
import redis
2019-05-02 15:31:14 +00:00
import random
2019-06-26 14:36:40 +00:00
import logging
import logging . handlers
2019-06-24 11:43:16 +00:00
import configparser
2019-07-31 11:24:43 +00:00
from flask import Flask , render_template , jsonify , request , Request , Response , session , redirect , url_for
2019-05-02 15:31:14 +00:00
from flask_login import LoginManager , current_user , login_user , logout_user , login_required
2019-05-03 14:52:05 +00:00
import bcrypt
2014-08-06 09:43:40 +00:00
import flask
2017-04-19 09:02:03 +00:00
import importlib
from os . path import join
2016-07-05 14:53:03 +00:00
sys . path . append ( os . path . join ( os . environ [ ' AIL_BIN ' ] , ' packages/ ' ) )
2017-04-19 09:02:03 +00:00
sys . path . append ( ' ./modules/ ' )
2016-07-05 14:53:03 +00:00
import Paste
2016-07-21 11:44:22 +00:00
from Date import Date
2014-12-24 14:42:20 +00:00
2019-05-02 15:31:14 +00:00
from User import User
2018-05-23 14:58:56 +00:00
from pytaxonomies import Taxonomies
2016-12-09 07:46:37 +00:00
# Import config
import Flask_config
2016-08-19 11:34:02 +00:00
2019-06-19 13:00:25 +00:00
# Import Role_Manager
2019-06-20 13:49:40 +00:00
from Role_Manager import create_user_db , check_password_strength , check_user_role_integrity
2019-06-19 15:02:09 +00:00
from Role_Manager import login_admin , login_analyst
2019-06-19 13:00:25 +00:00
2019-07-30 11:49:21 +00:00
Flask_dir = os . environ [ ' AIL_FLASK ' ]
2016-12-09 07:46:37 +00:00
# CONFIG #
cfg = Flask_config . cfg
2018-09-20 08:38:19 +00:00
baseUrl = cfg . get ( " Flask " , " baseurl " )
baseUrl = baseUrl . replace ( ' / ' , ' ' )
if baseUrl != ' ' :
baseUrl = ' / ' + baseUrl
2016-08-24 16:00:05 +00:00
2019-05-03 14:52:05 +00:00
# ========= REDIS =========#
r_serv_db = redis . StrictRedis (
host = cfg . get ( " ARDB_DB " , " host " ) ,
port = cfg . getint ( " ARDB_DB " , " port " ) ,
db = cfg . getint ( " ARDB_DB " , " db " ) ,
decode_responses = True )
r_serv_tags = redis . StrictRedis (
host = cfg . get ( " ARDB_Tags " , " host " ) ,
port = cfg . getint ( " ARDB_Tags " , " port " ) ,
db = cfg . getint ( " ARDB_Tags " , " db " ) ,
decode_responses = True )
2019-06-26 14:36:40 +00:00
r_cache = redis . StrictRedis (
host = cfg . get ( " Redis_Cache " , " host " ) ,
port = cfg . getint ( " Redis_Cache " , " port " ) ,
db = cfg . getint ( " Redis_Cache " , " db " ) ,
decode_responses = True )
# logs
log_dir = os . path . join ( os . environ [ ' AIL_HOME ' ] , ' logs ' )
if not os . path . isdir ( log_dir ) :
os . makedirs ( logs_dir )
2019-07-25 15:26:32 +00:00
# log_filename = os.path.join(log_dir, 'flask_server.logs')
# logger = logging.getLogger()
# formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# handler_log = logging.handlers.TimedRotatingFileHandler(log_filename, when="midnight", interval=1)
# handler_log.suffix = '%Y-%m-%d.log'
# handler_log.setFormatter(formatter)
# handler_log.setLevel(30)
# logger.addHandler(handler_log)
# logger.setLevel(30)
2019-06-26 14:36:40 +00:00
2019-05-03 14:52:05 +00:00
# ========= =========#
2019-06-24 11:43:16 +00:00
# ========= TLS =========#
ssl_context = ssl . SSLContext ( ssl . PROTOCOL_TLSv1_2 )
2019-07-30 11:49:21 +00:00
ssl_context . load_cert_chain ( certfile = os . path . join ( Flask_dir , ' server.crt ' ) , keyfile = os . path . join ( Flask_dir , ' server.key ' ) )
2019-06-24 11:43:16 +00:00
#print(ssl_context.get_ciphers())
# ========= =========#
2018-09-20 08:38:19 +00:00
Flask_config . app = Flask ( __name__ , static_url_path = baseUrl + ' /static/ ' )
2016-12-09 07:46:37 +00:00
app = Flask_config . app
2018-06-08 14:49:20 +00:00
app . config [ ' MAX_CONTENT_LENGTH ' ] = 900 * 1024 * 1024
2014-08-26 15:33:28 +00:00
2019-05-02 15:31:14 +00:00
# ========= session ========
app . secret_key = str ( random . getrandbits ( 256 ) )
login_manager = LoginManager ( )
login_manager . login_view = ' login '
login_manager . init_app ( app )
# ========= LOGIN MANAGER ========
@login_manager.user_loader
def load_user ( user_id ) :
return User . get ( user_id )
2017-04-25 10:18:08 +00:00
# ========= HEADER GENERATION ========
# Get headers items that should be ignored (not displayed)
toIgnoreModule = set ( )
try :
with open ( ' templates/ignored_modules.txt ' , ' r ' ) as f :
lines = f . read ( ) . splitlines ( )
for line in lines :
toIgnoreModule . add ( line )
except IOError :
2019-07-30 11:49:21 +00:00
f = open ( os . path . join ( Flask_dir , ' templates ' , ' ignored_modules.txt ' ) , ' w ' )
2017-04-25 10:18:08 +00:00
f . close ( )
2017-04-19 13:14:20 +00:00
# Dynamically import routes and functions from modules
# Also, prepare header.html
to_add_to_header_dico = { }
2019-07-30 11:49:21 +00:00
for root , dirs , files in os . walk ( os . path . join ( Flask_dir , ' modules ' ) ) :
2017-04-19 13:14:20 +00:00
sys . path . append ( join ( root ) )
2017-04-25 10:18:08 +00:00
# Ignore the module
curr_dir = root . split ( ' / ' ) [ 1 ]
if curr_dir in toIgnoreModule :
continue
2017-04-19 13:14:20 +00:00
for name in files :
module_name = root . split ( ' / ' ) [ - 2 ]
if name . startswith ( ' Flask_ ' ) and name . endswith ( ' .py ' ) :
if name == ' Flask_config.py ' :
continue
name = name . strip ( ' .py ' )
#print('importing {}'.format(name))
importlib . import_module ( name )
elif name == ' header_ {} .html ' . format ( module_name ) :
with open ( join ( root , name ) , ' r ' ) as f :
to_add_to_header_dico [ module_name ] = f . read ( )
2014-08-26 15:33:28 +00:00
2017-04-19 13:14:20 +00:00
#create header.html
complete_header = " "
2019-07-30 11:49:21 +00:00
with open ( os . path . join ( Flask_dir , ' templates ' , ' header_base.html ' ) , ' r ' ) as f :
2017-04-19 13:14:20 +00:00
complete_header = f . read ( )
modified_header = complete_header
#Add the header in the supplied order
2018-04-17 14:06:32 +00:00
for module_name , txt in list ( to_add_to_header_dico . items ( ) ) :
2017-04-19 13:14:20 +00:00
to_replace = ' <!-- {} --> ' . format ( module_name )
if to_replace in complete_header :
modified_header = modified_header . replace ( to_replace , txt )
del to_add_to_header_dico [ module_name ]
#Add the header for no-supplied order
to_add_to_header = [ ]
for module_name , txt in to_add_to_header_dico . items ( ) :
to_add_to_header . append ( txt )
modified_header = modified_header . replace ( ' <!--insert here--> ' , ' \n ' . join ( to_add_to_header ) )
#Write the header.html file
2019-07-30 11:49:21 +00:00
with open ( os . path . join ( Flask_dir , ' templates ' , ' header.html ' ) , ' w ' ) as f :
2017-04-19 13:14:20 +00:00
f . write ( modified_header )
# ========= JINJA2 FUNCTIONS ========
2016-07-05 14:53:03 +00:00
def list_len ( s ) :
return len ( s )
app . jinja_env . filters [ ' list_len ' ] = list_len
2016-08-04 09:55:38 +00:00
2016-08-09 09:59:36 +00:00
# ========= CACHE CONTROL ========
@app.after_request
def add_header ( response ) :
"""
Add headers to both force latest IE rendering engine or Chrome Frame ,
and also to cache the rendered page for 10 minutes .
"""
response . headers [ ' X-UA-Compatible ' ] = ' IE=Edge,chrome=1 '
response . headers [ ' Cache-Control ' ] = ' public, max-age=0 '
return response
2016-07-21 11:44:22 +00:00
2019-06-26 14:36:40 +00:00
# @app.route('/test', methods=['GET'])
# def test():
# for rule in app.url_map.iter_rules():
# print(rule)
# return 'o'
2017-04-19 13:14:20 +00:00
# ========== ROUTES ============
2019-05-02 15:31:14 +00:00
@app.route ( ' /login ' , methods = [ ' POST ' , ' GET ' ] )
def login ( ) :
2019-06-26 14:36:40 +00:00
current_ip = request . remote_addr
login_failed_ip = r_cache . get ( ' failed_login_ip: {} ' . format ( current_ip ) )
# brute force by ip
if login_failed_ip :
login_failed_ip = int ( login_failed_ip )
if login_failed_ip > = 5 :
error = ' Max Connection Attempts reached, Please wait {} s ' . format ( r_cache . ttl ( ' failed_login_ip: {} ' . format ( current_ip ) ) )
return render_template ( " login.html " , error = error )
2019-05-02 15:31:14 +00:00
if request . method == ' POST ' :
username = request . form . get ( ' username ' )
password = request . form . get ( ' password ' )
2019-06-06 19:27:13 +00:00
#next_page = request.form.get('next_page')
2019-05-02 15:31:14 +00:00
if username is not None :
user = User . get ( username )
2019-06-26 14:36:40 +00:00
login_failed_user_id = r_cache . get ( ' failed_login_user_id: {} ' . format ( username ) )
# brute force by user_id
if login_failed_user_id :
login_failed_user_id = int ( login_failed_user_id )
if login_failed_user_id > = 5 :
error = ' Max Connection Attempts reached, Please wait {} s ' . format ( r_cache . ttl ( ' failed_login_user_id: {} ' . format ( username ) ) )
return render_template ( " login.html " , error = error )
2019-05-02 15:31:14 +00:00
if user and user . check_password ( password ) :
2019-06-20 13:49:40 +00:00
if not check_user_role_integrity ( user . get_id ( ) ) :
error = ' Incorrect User ACL, Please contact your administrator '
return render_template ( " login.html " , error = error )
2019-05-02 15:31:14 +00:00
login_user ( user ) ## TODO: use remember me ?
2019-06-06 19:27:13 +00:00
if user . request_password_change ( ) :
return redirect ( url_for ( ' change_password ' ) )
else :
return redirect ( url_for ( ' dashboard.index ' ) )
2019-06-26 14:36:40 +00:00
# login failed
2019-05-02 15:31:14 +00:00
else :
2019-06-26 14:36:40 +00:00
# set brute force protection
2019-07-25 15:26:32 +00:00
#logger.warning("Login failed, ip={}, username={}".format(current_ip, username))
2019-06-26 14:36:40 +00:00
r_cache . incr ( ' failed_login_ip: {} ' . format ( current_ip ) )
r_cache . expire ( ' failed_login_ip: {} ' . format ( current_ip ) , 300 )
r_cache . incr ( ' failed_login_user_id: {} ' . format ( username ) )
r_cache . expire ( ' failed_login_user_id: {} ' . format ( username ) , 300 )
#
2019-06-20 13:49:40 +00:00
error = ' Password Incorrect '
return render_template ( " login.html " , error = error )
2019-05-02 15:31:14 +00:00
2019-06-20 13:49:40 +00:00
return ' please provide a valid username '
2019-05-02 15:31:14 +00:00
else :
2019-06-06 19:27:13 +00:00
#next_page = request.args.get('next')
2019-06-20 13:49:40 +00:00
error = request . args . get ( ' error ' )
return render_template ( " login.html " , error = error )
2019-06-06 19:27:13 +00:00
@app.route ( ' /change_password ' , methods = [ ' POST ' , ' GET ' ] )
@login_required
def change_password ( ) :
password1 = request . form . get ( ' password1 ' )
password2 = request . form . get ( ' password2 ' )
2019-06-20 08:56:31 +00:00
error = request . args . get ( ' error ' )
2019-06-06 19:27:13 +00:00
2019-06-20 08:56:31 +00:00
if error :
return render_template ( " change_password.html " , error = error )
2019-06-06 19:27:13 +00:00
2019-06-20 08:56:31 +00:00
if current_user . is_authenticated and password1 != None :
if password1 == password2 :
if check_password_strength ( password1 ) :
user_id = current_user . get_id ( )
create_user_db ( user_id , password1 , update = True )
return redirect ( url_for ( ' dashboard.index ' ) )
else :
error = ' Incorrect password '
return render_template ( " change_password.html " , error = error )
2019-06-06 19:27:13 +00:00
else :
2019-06-20 08:56:31 +00:00
error = " Passwords don ' t match "
return render_template ( " change_password.html " , error = error )
2019-06-06 19:27:13 +00:00
else :
2019-06-20 08:56:31 +00:00
error = ' Please choose a new password '
return render_template ( " change_password.html " , error = error )
2019-05-02 15:31:14 +00:00
@app.route ( ' /logout ' )
@login_required
def logout ( ) :
logout_user ( )
2019-05-03 14:52:05 +00:00
return redirect ( url_for ( ' login ' ) )
2019-06-19 15:02:09 +00:00
# role error template
@app.route ( ' /role ' , methods = [ ' POST ' , ' GET ' ] )
@login_required
def role ( ) :
return render_template ( " error/403.html " ) , 403
2017-04-19 13:14:20 +00:00
@app.route ( ' /searchbox/ ' )
2019-06-19 15:02:09 +00:00
@login_required
@login_analyst
2017-04-19 13:14:20 +00:00
def searchbox ( ) :
return render_template ( " searchbox.html " )
2019-06-20 08:11:23 +00:00
# ========== ERROR HANDLER ============
2019-07-31 11:24:43 +00:00
@app.errorhandler ( 405 )
def _handle_client_error ( e ) :
if request . path . startswith ( ' /api/ ' ) :
return Response ( json . dumps ( { " status " : " error " , " reason " : " Method Not Allowed: The method is not allowed for the requested URL " } , indent = 2 , sort_keys = True ) , mimetype = ' application/json ' ) , 405
else :
return e
2019-06-20 08:11:23 +00:00
@app.errorhandler ( 404 )
2019-07-31 11:24:43 +00:00
def error_page_not_found ( e ) :
if request . path . startswith ( ' /api/ ' ) :
return Response ( json . dumps ( { " status " : " error " , " reason " : " 404 Not Found " } , indent = 2 , sort_keys = True ) , mimetype = ' application/json ' ) , 404
else :
# avoid endpoint enumeration
return page_not_found ( e )
2019-06-20 08:11:23 +00:00
@login_required
def page_not_found ( e ) :
2019-06-20 08:56:31 +00:00
# avoid endpoint enumeration
2019-06-20 08:11:23 +00:00
return render_template ( ' error/404.html ' ) , 404
2017-04-19 13:14:20 +00:00
2018-05-23 14:58:56 +00:00
# ========== INITIAL taxonomies ============
# add default ail taxonomies
r_serv_tags . sadd ( ' active_taxonomies ' , ' infoleak ' )
2018-05-30 14:18:58 +00:00
r_serv_tags . sadd ( ' active_taxonomies ' , ' gdpr ' )
r_serv_tags . sadd ( ' active_taxonomies ' , ' fpf ' )
2018-05-23 14:58:56 +00:00
# add default tags
taxonomies = Taxonomies ( )
for tag in taxonomies . get ( ' infoleak ' ) . machinetags ( ) :
r_serv_tags . sadd ( ' active_tag_infoleak ' , tag )
2018-05-30 14:18:58 +00:00
for tag in taxonomies . get ( ' gdpr ' ) . machinetags ( ) :
2018-06-06 08:05:25 +00:00
r_serv_tags . sadd ( ' active_tag_gdpr ' , tag )
2018-05-30 14:18:58 +00:00
for tag in taxonomies . get ( ' fpf ' ) . machinetags ( ) :
2018-06-06 08:05:25 +00:00
r_serv_tags . sadd ( ' active_tag_fpf ' , tag )
2018-05-23 14:58:56 +00:00
2018-06-15 15:25:43 +00:00
# ========== INITIAL tags auto export ============
infoleak_tags = taxonomies . get ( ' infoleak ' ) . machinetags ( )
infoleak_automatic_tags = [ ]
for tag in taxonomies . get ( ' infoleak ' ) . machinetags ( ) :
if tag . split ( ' = ' ) [ 0 ] [ : ] == ' infoleak:automatic-detection ' :
r_serv_db . sadd ( ' list_export_tags ' , tag )
2018-06-19 14:39:49 +00:00
r_serv_db . sadd ( ' list_export_tags ' , ' infoleak:submission= " manual " ' )
2016-12-09 07:46:37 +00:00
# ============ MAIN ============
2016-07-08 08:19:24 +00:00
2014-08-06 09:43:40 +00:00
if __name__ == " __main__ " :
2019-06-24 11:43:16 +00:00
app . run ( host = ' 0.0.0.0 ' , port = 7000 , threaded = True , ssl_context = ssl_context )