fix: [Flask auth] add brute force and side-channel protection

pull/24/head
Terrtia 2019-09-03 10:43:52 +02:00
parent f6b6137937
commit 96cfebd0ea
No known key found for this signature in database
GPG Key ID: 1E1B1F50D84613D0
5 changed files with 30 additions and 18 deletions

View File

@ -13,6 +13,7 @@ sensor registrations, management of decoding protocols and dispatching to adequa
DB 0 - Stats + sensor configs DB 0 - Stats + sensor configs
DB 1 - Users DB 1 - Users
DB 2 - Analyzer queue DB 2 - Analyzer queue
DB 3 - Flask Cache
``` ```
### DB 1 ### DB 1

View File

@ -2,8 +2,10 @@
# -*-coding:UTF-8 -* # -*-coding:UTF-8 -*
import os import os
import time
import redis import redis
import bcrypt import bcrypt
import random
from flask_login import UserMixin from flask_login import UserMixin
@ -44,6 +46,9 @@ class User(UserMixin):
if self.user_is_anonymous(): if self.user_is_anonymous():
return False return False
rand_sleep = random.randint(1,300)/1000
time.sleep(rand_sleep)
password = password.encode() password = password.encode()
hashed_password = self.r_serv_db.hget('user:all', self.id).encode() hashed_password = self.r_serv_db.hget('user:all', self.id).encode()
if bcrypt.checkpw(password, hashed_password): if bcrypt.checkpw(password, hashed_password):

View File

@ -87,6 +87,12 @@ redis_server_analyzer = redis.StrictRedis(
db=2, db=2,
decode_responses=True) 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: with open(json_type_description_path, 'r') as f:
json_type = json.loads(f.read()) json_type = json.loads(f.read())
json_type_description = {} json_type_description = {}
@ -267,7 +273,6 @@ def _handle_client_error(e):
@app.route('/login', methods=['POST', 'GET']) @app.route('/login', methods=['POST', 'GET'])
def login(): def login():
'''
current_ip = request.remote_addr current_ip = request.remote_addr
login_failed_ip = r_cache.get('failed_login_ip:{}'.format(current_ip)) login_failed_ip = r_cache.get('failed_login_ip:{}'.format(current_ip))
@ -277,7 +282,6 @@ def login():
if login_failed_ip >= 5: if login_failed_ip >= 5:
error = 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip:{}'.format(current_ip))) 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) return render_template("login.html", error=error)
'''
if request.method == 'POST': if request.method == 'POST':
username = request.form.get('username') username = request.form.get('username')
@ -286,7 +290,7 @@ def login():
if username is not None: if username is not None:
user = User.get(username) user = User.get(username)
'''
login_failed_user_id = r_cache.get('failed_login_user_id:{}'.format(username)) login_failed_user_id = r_cache.get('failed_login_user_id:{}'.format(username))
# brute force by user_id # brute force by user_id
if login_failed_user_id: if login_failed_user_id:
@ -294,7 +298,6 @@ def login():
if login_failed_user_id >= 5: if login_failed_user_id >= 5:
error = 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_user_id:{}'.format(username))) 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) return render_template("login.html", error=error)
'''
if user and user.check_password(password): if user and user.check_password(password):
#if not check_user_role_integrity(user.get_id()): #if not check_user_role_integrity(user.get_id()):
@ -309,11 +312,10 @@ def login():
else: else:
# set brute force protection # set brute force protection
#logger.warning("Login failed, ip={}, username={}".format(current_ip, username)) #logger.warning("Login failed, ip={}, username={}".format(current_ip, username))
#r_cache.incr('failed_login_ip:{}'.format(current_ip)) r_cache.incr('failed_login_ip:{}'.format(current_ip))
#r_cache.expire('failed_login_ip:{}'.format(current_ip), 300) r_cache.expire('failed_login_ip:{}'.format(current_ip), 300)
#r_cache.incr('failed_login_user_id:{}'.format(username)) r_cache.incr('failed_login_user_id:{}'.format(username))
#r_cache.expire('failed_login_user_id:{}'.format(username), 300) r_cache.expire('failed_login_user_id:{}'.format(username), 300)
#
error = 'Password Incorrect' error = 'Password Incorrect'
return render_template("login.html", error=error) return render_template("login.html", error=error)

View File

@ -8,9 +8,11 @@
import os import os
import re import re
import sys import sys
import time
import uuid import uuid
import json import json
import redis import redis
import random
import datetime import datetime
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response
@ -42,6 +44,12 @@ r_serv_db = redis.StrictRedis(
db=1, db=1,
decode_responses=True) decode_responses=True)
r_cache = redis.StrictRedis(
host=host_redis_metadata,
port=port_redis_metadata,
db=3,
decode_responses=True)
# ============ AUTH FUNCTIONS ============ # ============ AUTH FUNCTIONS ============
def check_token_format(strg, search=re.compile(r'[^a-zA-Z0-9_-]').search): 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): if not check_token_format(token):
return False return False
rand_sleep = random.randint(1,300)/1000
time.sleep(rand_sleep)
if r_serv_db.hexists('user:tokens', token): if r_serv_db.hexists('user:tokens', token):
return True return True
else: else:
@ -104,7 +114,6 @@ def authErrors(user_role):
data = None data = None
# verify token format # verify token format
'''
# brute force protection # brute force protection
current_ip = request.remote_addr current_ip = request.remote_addr
login_failed_ip = r_cache.get('failed_login_ip_api:{}'.format(current_ip)) 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) login_failed_ip = int(login_failed_ip)
if login_failed_ip >= 5: 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) return ({'status': 'error', 'reason': 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip_api:{}'.format(current_ip)))}, 401)
'''
try: try:
authenticated = False authenticated = False
@ -125,8 +133,8 @@ def authErrors(user_role):
data = ({'status': 'error', 'reason': 'Access Forbidden'}, 403) data = ({'status': 'error', 'reason': 'Access Forbidden'}, 403)
if not authenticated: if not authenticated:
#r_cache.incr('failed_login_ip_api:{}'.format(current_ip)) r_cache.incr('failed_login_ip_api:{}'.format(current_ip))
#r_cache.expire('failed_login_ip_api:{}'.format(current_ip), 300) r_cache.expire('failed_login_ip_api:{}'.format(current_ip), 300)
data = ({'status': 'error', 'reason': 'Authentication failed'}, 401) data = ({'status': 'error', 'reason': 'Authentication failed'}, 401)
except Exception as e: except Exception as e:
print(e) print(e)

View File

@ -45,11 +45,7 @@ def one():
return 1 return 1
def check_email(email): def check_email(email):
result = email_regex.match(email) return email_regex.match(email)
if result:
return True
else:
return False
def get_user_metadata(user_id): def get_user_metadata(user_id):
user_metadata = {} user_metadata = {}