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

Ramon Medeiros ramonn at linux.vnet.ibm.com
Thu Jan 26 10:42:17 UTC 2017



On 1/25/17 3:46 PM, Aline Manera wrote:
>
>
> On 01/24/2017 06:12 PM, ramonn at linux.vnet.ibm.com wrote:
>> From: Ramon Medeiros <ramonn at linux.vnet.ibm.com>
>>
>> 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:
>>
>> v3:
>> Improve error handling on login page
>>
>> v2:
>> Set timeout by user, ip and session id. This will avoid trouble with
>> users using the same ip, like NAT.
>>
>>   src/wok/i18n.py          |  2 ++
>>   src/wok/root.py          | 66 
>> ++++++++++++++++++++++++++++++++++++++++++++----
>>   ui/js/src/wok.login.js   | 21 +++++++++------
>>   ui/pages/i18n.json.tmpl  |  5 +++-
>>   ui/pages/login.html.tmpl |  6 ++---
>>   5 files changed, 82 insertions(+), 18 deletions(-)
>>
>> diff --git a/src/wok/i18n.py b/src/wok/i18n.py
>> index e454e31..7d595b8 100644
>> --- a/src/wok/i18n.py
>> +++ b/src/wok/i18n.py
>> @@ -41,7 +41,9 @@ 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."),
>>       "WOKAUTH0005E": _("Invalid LDAP configuration: %(item)s : 
>> %(value)s"),
>> +    "WOKAUTH0006E": _("The username or password you entered is 
>> incorrect. Please try again."),
>>
>>       "WOKLOG0001E": _("Invalid filter parameter. Filter parameters 
>> allowed: %(filters)s"),
>>       "WOKLOG0002E": _("Creation of log file failed: %(err)s"),
>> diff --git a/src/wok/root.py b/src/wok/root.py
>> index 080b7f0..d314d25 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
>>   #
>> @@ -21,7 +21,9 @@
>>
>>   import cherrypy
>>   import json
>> +import re
>>   import os
>> +import time
>>   from distutils.version import LooseVersion
>>
>>   from wok import auth
>> @@ -31,7 +33,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 +50,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', 'error_page.401']
>>
>>           if not dev_env:
>>               self._cp_config = dict([(key, 
>> self.error_production_handler)
>> @@ -146,6 +149,7 @@ class WokRoot(Root):
>>           self.domain = 'wok'
>>           self.messages = messages
>>           self.extends = None
>> +        self.failed_logins = {}
>>
>>           # set user log messages and make sure all parameters are 
>> present
>>           self.log_map = ROOT_REQUESTS
>> @@ -153,6 +157,13 @@ class WokRoot(Root):
>>
>>       @cherrypy.expose
>>       def login(self, *args):
>> +        def _raise_timeout(user_id):
>> +            length = self.failed_logins[user_ip_sid]["count"]
>> +            timeout = (length - 3) * 30
>> +            details = e = UnauthorizedError("WOKAUTH0004E",
>> +                                            {"seconds": timeout})
>> +            log_request(code, params, details, method, 403)
>> +            raise cherrypy.HTTPError(403, e.message)
>>           details = None
>>           method = 'POST'
>>           code = self.getRequestMessage(method, 'login')
>> @@ -161,17 +172,62 @@ class WokRoot(Root):
>>               params = parse_request()
>>               username = params['username']
>>               password = params['password']
>> +
>
>
> It would be good to use strip() around username and password to avoid 
> multiple white spaces.
>
>> +            # no data passed: raise error
>> +            if len(username + password) == 0:
>> +                raise KeyError
>> +
>
> What is this for? You are summing 'username' and 'password' and get 
> the length of it. I am not sure is that what you want.
>
> If it is to identify a missing parameter you should check them 
> individually. As username AND password should be a string with length 
> != 0
Well,

when parse_request run, it never get a missing parameters, so i need to 
check if the var is empty. I will check each one alone.
>
>>           except KeyError, item:
>>               details = e = MissingParameter('WOKAUTH0003E', {'item': 
>> str(item)})
>>               log_request(code, params, details, method, 400)
>>               raise cherrypy.HTTPError(400, e.message)
>>
>> +        # get authentication info
>> +        remote_ip = cherrypy.request.remote.ip
>> +        session_id = str(cherrypy.session.originalid)
>> +        user_ip_sid = re.escape(username + remote_ip + session_id)
>> +
>> +        # check for repetly
>> +        count = self.failed_logins.get(user_ip_sid, {"count": 
>> 0}).get("count")
>> +        if count >= 3:
>> +
>> +                # verify if timeout is still valid
>> +                last_try = self.failed_logins[user_ip_sid]["time"]
>> +                if time.time() < (last_try + ((count - 3) * 30)):
>> +                    _raise_timeout(user_ip_sid)
>
> else:
>     # remove the entry from the dict
>
ok.
>> +
>>           try:
>>               status = 200
>>               user_info = auth.login(username, password)
>> +
>> +            # user logged sucessfuly: reset counters
>> +            if self.failed_logins.get(user_ip_sid) != None:
>> +                self.failed_logins.remove(user_ip_sid)
>>           except cherrypy.HTTPError, e:
>> -            status = e.status
>> -            raise
>> +
>> +            # store time and prevent too much tries
>> +            if self.failed_logins.get(user_ip_sid) == None:
>> +                self.failed_logins[user_ip_sid] = {"time": time.time(),
>> +                                                   "ip": remote_ip,
>> + "session_id": session_id,
>> +                                                   "username": 
>> username,
>> +                                                   "count": 1}
>> +            else:
>> +
>> +                # tries take more than 30 seconds between each one: 
>> do not
>> +                # increase count
>> +                if (time.time() -
>> + self.failed_logins[user_ip_sid]["time"]) < 30:
>> +
>> +                    self.failed_logins[user_ip_sid]["time"] = 
>> time.time()
>> +                    self.failed_logins[user_ip_sid]["count"] += 1
>> +
>> +            # more than 3 fails: raise error
>> +            if self.failed_logins[user_ip_sid]["count"] > 3:
>> +                _raise_timeout(user_ip_sid)
>> +
>
>> +            details = e = MissingParameter('WOKAUTH0006E')
>> +            raise cherrypy.HTTPError(401, e.message)
>>           finally:
>>               log_request(code, params, details, method, status)
>>
>> diff --git a/ui/js/src/wok.login.js b/ui/js/src/wok.login.js
>> index fa2a98a..d9daacf 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
>>    *
>> @@ -19,6 +19,10 @@
>>    */
>>   wok.login_main = function() {
>>       "use strict";
>> +    var i18n;
>> +    wok.getI18n(function(i18nObj){
>> +        i18n = i18nObj;
>> +     }, false, "i18n.json", true);
>>
>>       // verify if language is available
>>       var selectedLanguage = wok.lang.get();
>> @@ -50,7 +54,8 @@ wok.login_main = function() {
>>       var query = window.location.search;
>>       var error = /.*error=(.*?)(&|$)/g.exec(query);
>>       if (error && error[1] === "sessionTimeout") {
>> -        $("#messSession").show();
>> +        $("#errorArea").html(i18n["WOKAUT0001E"]);
>> +        $("#errorArea").show();
>>       }
>>
>>       var userNameBox = $('#username');
>> @@ -82,13 +87,13 @@ wok.login_main = function() {
>> window.location.replace(window.location.pathname.replace(/\/+login.html/, 
>> '') + next_url);
>>           }, function(jqXHR, textStatus, errorThrown) {
>>               if (jqXHR.responseText == "") {
>> -                $("#messUserPass").hide();
>> -                $("#missServer").show();
>> -            } else {
>> -                $("#missServer").hide();
>> -                $("#messUserPass").show();
>> +                $("#errorArea").html(i18n["WOKAUT0002E"]);
>> +                $("#errorArea").show();
>> +            } else if ((jqXHR.responseJSON != undefined) &&
>> +                       ! (jqXHR.responseJSON["reason"] == undefined)) {
>> + $("#errorArea").html(jqXHR.responseJSON["reason"]);
>> +                $("#errorArea").show();
>>               }
>> -            $("#messSession").hide();
>>               $("#logging").hide();
>>               $("#login").show();
>>           });
>> diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
>> index ba29532..4329ad0 100644
>> --- a/ui/pages/i18n.json.tmpl
>> +++ b/ui/pages/i18n.json.tmpl
>> @@ -1,7 +1,7 @@
>>   #*
>>    * Project Wok
>>    *
>> - * Copyright IBM Corp, 2014-2016
>> + * Copyright IBM Corp, 2014-2017
>>    *
>>    * Code derived from Project Kimchi
>>    *
>> @@ -39,6 +39,9 @@
>>
>>       "WOKHOST6001M": "$_("Max:")",
>>
>> +    "WOKAUT0001E": "$_("Session timeout, please re-login.")",
>> +    "WOKAUT0002E": "$_("Server unreachable")",
>> +
>>       "WOKSETT0001M": "$_("Application")",
>>       "WOKSETT0002M": "$_("User")",
>>       "WOKSETT0003M": "$_("Request")",
>> diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl
>> index f5a4b2d..6f967cf 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
>>    *
>> @@ -104,9 +104,7 @@
>>           <div class="container">
>>               <div id="login-window" class="login-area row">
>>                   <div class="err-area">
>> -                    <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="errorArea" class="alert alert-danger" 
>> style="display: none;"></div>
>>                   </div>
>>                   <form id="form-login" class="form-horizontal" 
>> method="post">
>>                       <div class="form-group">
>



More information about the Kimchi-devel mailing list