From ec4a099f182629d86a7421af7d4899a655be684e Mon Sep 17 00:00:00 2001 From: 0livd Date: Sun, 20 Aug 2017 12:37:12 +0200 Subject: Protect admin endpoints against brute force attacks (#249) * Protect admin endpoints against brute force attacks Add a throttling mechanism to prevent a client brute forcing the authentication form, based on its ip address Closes #245 * Reset attempt counters if they get memory hungry --- ihatemoney/web.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'ihatemoney/web.py') diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 65c0ed6..cc2eeac 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -27,10 +27,12 @@ from ihatemoney.forms import ( InviteForm, MemberForm, PasswordReminder, ProjectForm, get_billform_for, ExportForm ) -from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv +from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv, LoginThrottler main = Blueprint("main", __name__) +login_throttler = LoginThrottler(max_attempts=3, delay=1) + def requires_admin(f): """Require admin permissions for @requires_admin decorated endpoints. @@ -89,14 +91,24 @@ def admin(): form = AdminAuthenticationForm() goto = request.args.get('goto', url_for('.home')) if request.method == "POST": + client_ip = request.remote_addr + if not login_throttler.is_login_allowed(client_ip): + msg = _("Too many failed login attempts, please retry later.") + form.errors['admin_password'] = [msg] + return render_template("authenticate.html", form=form, admin_auth=True) if form.validate(): - if check_password_hash(current_app.config['ADMIN_PASSWORD'], form.admin_password.data): + # Valid password + if (check_password_hash(current_app.config['ADMIN_PASSWORD'], + form.admin_password.data)): session['is_admin'] = True session.update() + login_throttler.reset(client_ip) return redirect(goto) - else: - msg = _("This admin password is not the right one") - form.errors['admin_password'] = [msg] + # Invalid password + login_throttler.increment_attempts_counter(client_ip) + msg = _("This admin password is not the right one. Only %(num)d attempts left.", + num=login_throttler.get_remaining_attempts(client_ip)) + form.errors['admin_password'] = [msg] return render_template("authenticate.html", form=form, admin_auth=True) -- cgit v1.1