Open jwag956 opened 4 years ago
IMHO this should be primarily implemented by things like OSSEC and fail2ban, those tools can also be more effective at deploying countermeasures. Providing a paragraph in documentation that one of them should be set up would be quite good already.
I solved this way:
In my user_model:
wrong_pwd_counter = db.Column(db.Integer)
and
`
class CustomLoginForm(LoginForm):
def validate(self):
response = True
if not super(LoginForm, self).validate():
response = False
self.user = _security.datastore.get_user(self.email.data)
if response and not self.user.is_active:
self.email.errors.append(get_message("DISABLED_ACCOUNT")[0])
response = False
if response and self.user is None:
self.email.errors.append(get_message("USER_DOES_NOT_EXIST")[0])
hash_password(self.password.data)
response = False
if response and not self.user.password:
self.password.errors.append(get_message("PASSWORD_NOT_SET")[0])
hash_password(self.password.data)
response = False
if response and not self.user.verify_and_update_password(self.password.data):
self.password.errors.append(get_message("INVALID_PASSWORD")[0])
response = False
if response and requires_confirmation(self.user):
self.email.errors.append(get_message("CONFIRMATION_REQUIRED")[0])
response = False
if not response and 'password' in self.errors.keys():
self.user.wrong_pwd_counter += 1
if self.user.wrong_pwd_counter >= Config.max_wrong_pwd:
self.user.active = False
notify_account_locked(self.user.email)
self.user.save()
if response:
self.user.wrong_pwd_counter = 0
self.user.save()
if not self.user.can_login:
self.password.errors.append('Cannot access. Call assistance')
response = False
return response
`
Would be good if it had something for this enabled by default.
I solved this partially with flask limit.
Also using real time black hole lists, and a CDN in front that has it's own security lists/WAF, and such... But still they get through. They are very, very sneaky (hundreds of proxy IPs, and using up all the attempts per ip for example).
However, it depends on your user base. If your organization has 1000s of people on one ip address, then these blocks fail. With industrial NAT becoming more common in some parts of the world and with ipv4s having run out - this will only get worse. Still, it's probably worth enabling something by default.
(ps. thanks lots for maintaining this!)
OWASP https://github.com/OWASP/ASVS/blob/master/4.0/en/0x11-V2-Authentication.md#v21-password-security-requirements 2.2.1 talks about brute force mitigation:
Verify that no more than 100 failed attempts per hour is possible on a single account.
This can probably be implemented as part of tracking. Could also add slowing down responses (but of course attacker can send multiple requests that would be handled by different threads).
Some good background: https://security.stackexchange.com/questions/85435/silently-limiting-login-attempts?rq=1 In particular - watch out for telling people an account is locked out since that relays info that the account/username is valid! (send email/SMS instead).
And this one: https://security.stackexchange.com/questions/74211/what-is-the-difference-between-login-throttling-and-temporary-account-lockout
NIST 5.2.2 - Unless otherwise specified in the description of a given authenticator, the verifier SHALL limit consecutive failed authentication attempts on a single account to no more than 100. and then... When the subscriber successfully authenticates, the verifier SHOULD disregard any previous failed attempts for that user from the same IP address.
What is confusing in the NIST verbiage is the first sentence talks about 'account' the second talks about IP addresses.