[PATCH][Wok] Bug fix #147: Block authentication request after too many failures

To prevent brute force attack, creates a mechanism to allow 3 tries first. After that, a timeout will start and will be added 30 seconds for each failed try in a row. Signed-off-by: Ramon Medeiros <ramonn@linux.vnet.ibm.com> --- src/wok/i18n.py | 3 ++- src/wok/root.py | 37 ++++++++++++++++++++++++++++++++++--- ui/js/src/wok.login.js | 11 ++++++++++- ui/pages/login.html.tmpl | 3 ++- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/wok/i18n.py b/src/wok/i18n.py index cbc37cf..c594ae1 100644 --- a/src/wok/i18n.py +++ b/src/wok/i18n.py @@ -1,7 +1,7 @@ # # Project Wok # -# Copyright IBM Corp, 2015-2016 +# Copyright IBM Corp, 2015-2017 # # Code derived from Project Kimchi # @@ -41,6 +41,7 @@ messages = { "WOKAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"), "WOKAUTH0002E": _("You are not authorized to access Kimchi"), "WOKAUTH0003E": _("Specify %(item)s to login into Kimchi"), + "WOKAUTH0004E": _("You have failed to login in too much attempts. Please, wait for %(seconds)s seconds to try again."), "WOKAUTH0005E": _("Invalid LDAP configuration: %(item)s : %(value)s"), "WOKLOG0001E": _("Invalid filter parameter. Filter parameters allowed: %(filters)s"), diff --git a/src/wok/root.py b/src/wok/root.py index 080b7f0..44e7adb 100644 --- a/src/wok/root.py +++ b/src/wok/root.py @@ -1,7 +1,7 @@ # # Project Wok # -# Copyright IBM Corp, 2015-2016 +# Copyright IBM Corp, 2015-2017 # # Code derived from Project Kimchi # @@ -22,6 +22,7 @@ import cherrypy import json import os +import time from distutils.version import LooseVersion from wok import auth @@ -31,7 +32,7 @@ from wok.config import paths as wok_paths from wok.control import sub_nodes from wok.control.base import Resource from wok.control.utils import parse_request -from wok.exception import MissingParameter +from wok.exception import MissingParameter, UnauthorizedError from wok.reqlogger import log_request @@ -48,7 +49,8 @@ class Root(Resource): super(Root, self).__init__(model) self._handled_error = ['error_page.400', 'error_page.404', 'error_page.405', 'error_page.406', - 'error_page.415', 'error_page.500'] + 'error_page.415', 'error_page.500', + 'error_page.403'] if not dev_env: self._cp_config = dict([(key, self.error_production_handler) @@ -146,6 +148,8 @@ class WokRoot(Root): self.domain = 'wok' self.messages = messages self.extends = None + self.failed_logins = [] + self.fail_timeout = 30 # set user log messages and make sure all parameters are present self.log_map = ROOT_REQUESTS @@ -153,6 +157,12 @@ class WokRoot(Root): @cherrypy.expose def login(self, *args): + def _raise_timeout(): + details = e = UnauthorizedError("WOKAUTH0004E", + {"seconds": self.fail_timeout}) + log_request(code, params, details, method, 403) + raise cherrypy.HTTPError(403, e.message) + details = None method = 'POST' code = self.getRequestMessage(method, 'login') @@ -166,10 +176,31 @@ class WokRoot(Root): log_request(code, params, details, method, 400) raise cherrypy.HTTPError(400, e.message) + # check for repetly + l = len(self.failed_logins) + if l >= 3: + + # verify if timeout is still valid + last_try = self.failed_logins[l - 1] + if time.time() < (last_try["time"] + self.fail_timeout): + _raise_timeout() try: status = 200 user_info = auth.login(username, password) + + # user logged sucessfuly: reset counters + self.failed_logins = [] + self.timeout = 30 except cherrypy.HTTPError, e: + # store time and prevent too much tries + self.failed_logins.append({"user": username, + "time": time.time()}) + + # more than 3 fails: raise error + if len(self.failed_logins) > 3: + self.fail_timeout += (len(self.failed_logins) - 3) * 30 + _raise_timeout() + status = e.status raise finally: diff --git a/ui/js/src/wok.login.js b/ui/js/src/wok.login.js index fa2a98a..ba9ac23 100644 --- a/ui/js/src/wok.login.js +++ b/ui/js/src/wok.login.js @@ -1,7 +1,7 @@ /* * Project Wok * - * Copyright IBM Corp, 2015-2016 + * Copyright IBM Corp, 2015-2017 * * Code derived from Project Kimchi * @@ -71,6 +71,7 @@ wok.login_main = function() { wok.login(settings, function(data) { var query = window.location.search; var next = /.*next=(.*?)(&|$)/g.exec(query); + if (next) { var next_url = decodeURIComponent(next[1]); } @@ -84,9 +85,17 @@ wok.login_main = function() { if (jqXHR.responseText == "") { $("#messUserPass").hide(); $("#missServer").show(); + $("#timeoutError").hide(); + } else if ((jqXHR.responseJSON != undefined) && + ! (jqXHR.responseJSON["reason"] == undefined)) { + $("#messUserPass").hide(); + $("#missServer").hide(); + $("#timeoutError").html(jqXHR.responseJSON["reason"]); + $("#timeoutError").show(); } else { $("#missServer").hide(); $("#messUserPass").show(); + $("#timeoutError").hide(); } $("#messSession").hide(); $("#logging").hide(); diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl index f5a4b2d..d25910c 100644 --- a/ui/pages/login.html.tmpl +++ b/ui/pages/login.html.tmpl @@ -1,7 +1,7 @@ #* * Project Wok * - * Copyright IBM Corp, 2014-2016 + * Copyright IBM Corp, 2014-2017 * * Code derived from Project Kimchi * @@ -107,6 +107,7 @@ <div id="messUserPass" class="alert alert-danger" style="display: none;">$_("The username or password you entered is incorrect. Please try again.")</div> <div id="messSession" class="alert alert-danger" style="display: none;">$_("Session timeout, please re-login.")</div> <div id="missServer" class="alert alert-danger" style="display: none;">$_("Server unreachable.")</div> + <div id="timeoutError" class="alert alert-danger" style="display: none;">$_("Timeout error")</div> </div> <form id="form-login" class="form-horizontal" method="post"> <div class="form-group"> -- 2.7.4
participants (1)
-
Ramon Medeiros