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

Lucio Correia luciojhc at linux.vnet.ibm.com
Tue Jan 17 12:12:24 UTC 2017


On 16/01/2017 17:29, Ramon Medeiros wrote:
> 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 at linux.vnet.ibm.com>
> ---
> Changes:
>
> v2:
> fix pep8 issues
>
>  src/wok/i18n.py          |  1 +
>  src/wok/root.py          | 37 ++++++++++++++++++++++++++++++++++---
>  ui/js/src/wok.login.js   | 11 ++++++++++-
>  ui/pages/login.html.tmpl |  3 ++-
>  4 files changed, 47 insertions(+), 5 deletions(-)
>
> diff --git a/src/wok/i18n.py b/src/wok/i18n.py
> index e454e31..21cc4ea 100644
> --- a/src/wok/i18n.py
> +++ b/src/wok/i18n.py
> @@ -41,6 +41,7 @@ messages = {
>      "WOKAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"),
>      "WOKAUTH0002E": _("You are not authorized to access Wok. Please, login first."),
>      "WOKAUTH0003E": _("Specify %(item)s to login into Wok."),
> +    "WOKAUTH0004E": _("You have failed to login in too much attempts. Please, wait for %(seconds)s seconds to try again."),

Suggestion: Too many login attempts failed. Wait %(seconds)s 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..65226fb 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 repestly
> +        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()
> +

I was thinking it would be controlled by username i.e. 3 failed attempts 
for the same username would block that username only.


>              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">
>


-- 
Lucio Correia



More information about the Kimchi-devel mailing list