Hi,
I would like to propose a little saver authenticator which makes it
very hard to brute-force the login for a known user. The idea is to
recognize unsuccessful logins and block them even if the attacker
guesses the right password eventually. This works only nice in
situations when the number of valid users is small since we keep an
access-db in memory for all known users. If one has to handle
thousands of users, one can add a purge and re-create method for the
access-db. Using vdb might not be clever in this situation, anyhow.
For all known users the access-db memories a count for the number of
unsuccessful tries and the time of the last unsuccessful try. In case
of a successful login by the TLS handshake, it is then checked if more
than max_retry (3) unsuccessful tries happened in the recent past
(about 4 to 20 min. depending on the number of tries) and if there was
a recent try then the connection will be refused even if the TLS
handshake was successful.
A complete successful login resets the try-count.
The weakness of this approach is that an attacker who guesses the user
name correctly can block a valid user and prevent him from using the
service simply by issuing a try of a false password every minute. This
might be a problems in situations when the server is used by an
automatic system. One should keep the user name secret and one should
have several alternating user names, which are automatically switched
by the client if a login fails.
Alternatively the option bypass_known_ip can be activated, but this
weakens the security because the attacker can brute-force the login as
soon as he can fake an IP address which bypasses the logic.
Greetings
Ruediger
class AccessDbAuthenticator(VdbAuthenticator):
wait_time=60.0 # 1 min * number of non successful tries
max_wait_time=600.0 # 10 min.
valid_retries=3 # allow 3 valid retries
bypass_known_ip=False
def __init__(self,vdb):
VdbAuthenticator.__init__(self,vdb)
self.lastaccess={}
self.users=[]
self.update_accessdb()
@classmethod
def from_dict(cls, users):
inst = cls(tlsapi.VerifierDB())
for username, password in users.iteritems():
inst.set_user(username, password)
inst.update_accessdb()
return inst
def update_accessdb(self):
self.users=self.list_users()
self.lastaccess={}
for u in self.users:
self.lastaccess[u]={'last_try':None, 'no_success':0,
'success_ip':[]}
return
def set_user(self, username, password):
VdbAuthenticator.set_user(self, username, password)
self.update_accessdb()
def del_user(self, username):
VdbAuthenticator.del_user(self, username)
self.update_accessdb()
def __call__(self, sock):
h, p = sock.getpeername()
sock2 = tlsapi.TLSConnection(sock)
sock2.fileno = lambda fd=sock.fileno(): fd # tlslite
omitted fileno
try:
sock2.handshakeServer(verifierDB = self.vdb)
except Exception:
if sock2.allegedSrpUsername!='':
tries=''
if sock2.allegedSrpUsername in self.users:
self.lastaccess[sock2.allegedSrpUsername]
['last_try']=time.clock()
self.lastaccess[sock2.allegedSrpUsername]
['no_success']+=1
tries="(%s tries)" %
str(self.lastaccess[sock2.allegedSrpUsername]['no_success'])
raise AuthenticationError("Bad try for user %s %s" %
(sock2.allegedSrpUsername,tries))
LA=self.lastaccess[sock2.allegedSrpUsername]
if (self.bypass_known_ip and not (h in LA['success_ip'])) and
(LA['no_success']>self.valid_retries) and (time.clock() -
LA['last_try']<min(LA['no_success']*self.wait_time,self.max_wait_time)):
LA['no_success']+=1
LA['last_try']=time.clock()
raise AuthenticationError("Repeated login failed for %s
(%s tries)" % (sock2.allegedSrpUsername,LA['no_success']))
else:
LA['no_success']=0
LA['last_try']=None
if not (h in LA['success_ip']):
LA['success_ip'].append(h)
return sock2, sock2.allegedSrpUsername