From 96cfebd0eaf42b64d17fc7ca123a6b8b1d53cf00 Mon Sep 17 00:00:00 2001 From: Terrtia Date: Tue, 3 Sep 2019 10:43:52 +0200 Subject: [PATCH] fix: [Flask auth] add brute force and side-channel protection --- server/documentation/Database.md | 1 + server/lib/User.py | 5 +++++ server/web/Flask_server.py | 20 +++++++++++--------- server/web/blueprints/restApi.py | 16 ++++++++++++---- server/web/blueprints/settings.py | 6 +----- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/server/documentation/Database.md b/server/documentation/Database.md index 51c13b9..56a727a 100644 --- a/server/documentation/Database.md +++ b/server/documentation/Database.md @@ -13,6 +13,7 @@ sensor registrations, management of decoding protocols and dispatching to adequa DB 0 - Stats + sensor configs DB 1 - Users DB 2 - Analyzer queue + DB 3 - Flask Cache ``` ### DB 1 diff --git a/server/lib/User.py b/server/lib/User.py index 74552a7..4345bbb 100755 --- a/server/lib/User.py +++ b/server/lib/User.py @@ -2,8 +2,10 @@ # -*-coding:UTF-8 -* import os +import time import redis import bcrypt +import random from flask_login import UserMixin @@ -44,6 +46,9 @@ class User(UserMixin): if self.user_is_anonymous(): return False + rand_sleep = random.randint(1,300)/1000 + time.sleep(rand_sleep) + password = password.encode() hashed_password = self.r_serv_db.hget('user:all', self.id).encode() if bcrypt.checkpw(password, hashed_password): diff --git a/server/web/Flask_server.py b/server/web/Flask_server.py index f8ea51b..7197485 100755 --- a/server/web/Flask_server.py +++ b/server/web/Flask_server.py @@ -87,6 +87,12 @@ redis_server_analyzer = redis.StrictRedis( db=2, decode_responses=True) +r_cache = redis.StrictRedis( + host=host_redis_metadata, + port=port_redis_metadata, + db=3, + decode_responses=True) + with open(json_type_description_path, 'r') as f: json_type = json.loads(f.read()) json_type_description = {} @@ -267,7 +273,6 @@ def _handle_client_error(e): @app.route('/login', methods=['POST', 'GET']) def login(): - ''' current_ip = request.remote_addr login_failed_ip = r_cache.get('failed_login_ip:{}'.format(current_ip)) @@ -277,7 +282,6 @@ def login(): 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) - ''' if request.method == 'POST': username = request.form.get('username') @@ -286,7 +290,7 @@ def login(): if username is not None: user = User.get(username) - ''' + login_failed_user_id = r_cache.get('failed_login_user_id:{}'.format(username)) # brute force by user_id if login_failed_user_id: @@ -294,7 +298,6 @@ def login(): 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) - ''' if user and user.check_password(password): #if not check_user_role_integrity(user.get_id()): @@ -309,11 +312,10 @@ def login(): else: # set brute force protection #logger.warning("Login failed, ip={}, username={}".format(current_ip, username)) - #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) - # + 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) error = 'Password Incorrect' return render_template("login.html", error=error) diff --git a/server/web/blueprints/restApi.py b/server/web/blueprints/restApi.py index 455e859..ce2a97c 100644 --- a/server/web/blueprints/restApi.py +++ b/server/web/blueprints/restApi.py @@ -8,9 +8,11 @@ import os import re import sys +import time import uuid import json import redis +import random import datetime from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response @@ -42,6 +44,12 @@ r_serv_db = redis.StrictRedis( db=1, decode_responses=True) +r_cache = redis.StrictRedis( + host=host_redis_metadata, + port=port_redis_metadata, + db=3, + decode_responses=True) + # ============ AUTH FUNCTIONS ============ def check_token_format(strg, search=re.compile(r'[^a-zA-Z0-9_-]').search): @@ -54,6 +62,8 @@ def verify_token(token): if not check_token_format(token): return False + rand_sleep = random.randint(1,300)/1000 + time.sleep(rand_sleep) if r_serv_db.hexists('user:tokens', token): return True else: @@ -104,7 +114,6 @@ def authErrors(user_role): data = None # verify token format - ''' # brute force protection current_ip = request.remote_addr login_failed_ip = r_cache.get('failed_login_ip_api:{}'.format(current_ip)) @@ -113,7 +122,6 @@ def authErrors(user_role): login_failed_ip = int(login_failed_ip) if login_failed_ip >= 5: return ({'status': 'error', 'reason': 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip_api:{}'.format(current_ip)))}, 401) - ''' try: authenticated = False @@ -125,8 +133,8 @@ def authErrors(user_role): data = ({'status': 'error', 'reason': 'Access Forbidden'}, 403) if not authenticated: - #r_cache.incr('failed_login_ip_api:{}'.format(current_ip)) - #r_cache.expire('failed_login_ip_api:{}'.format(current_ip), 300) + r_cache.incr('failed_login_ip_api:{}'.format(current_ip)) + r_cache.expire('failed_login_ip_api:{}'.format(current_ip), 300) data = ({'status': 'error', 'reason': 'Authentication failed'}, 401) except Exception as e: print(e) diff --git a/server/web/blueprints/settings.py b/server/web/blueprints/settings.py index abd63c4..8ba071f 100644 --- a/server/web/blueprints/settings.py +++ b/server/web/blueprints/settings.py @@ -45,11 +45,7 @@ def one(): return 1 def check_email(email): - result = email_regex.match(email) - if result: - return True - else: - return False + return email_regex.match(email) def get_user_metadata(user_id): user_metadata = {}