Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

brute force vulnerability #231

Closed
futuretap opened this issue Apr 30, 2020 · 4 comments · Fixed by #235
Closed

brute force vulnerability #231

futuretap opened this issue Apr 30, 2020 · 4 comments · Fixed by #235
Assignees

Comments

@futuretap
Copy link

futuretap commented Apr 30, 2020

Sorcery 0.14.0 is vulnerable to brute force attacks even if a login_lock_time_period is configured.

If an attacker continues their login attempts after the initial login_lock_time_period has passed, Sorcery no longer rejects those login attempts until eventually the attacker has guessed the right password.

This is caused by an early return in model/submodules/brute_force_protection.rb:68 which skips the update of the lock_expires_at field. So failed attempts don't update lock_expires_at as long as lock_expires_at contains any date, even if already passed.

I'll provide a fix.

futuretap added a commit to futuretap/sorcery that referenced this issue Apr 30, 2020
extend the account lock for subsequent failed login attempts after the initial lock period
futuretap added a commit to futuretap/sorcery that referenced this issue May 1, 2020
extend the account lock for subsequent failed login attempts after the initial lock period
@joshbuker
Copy link
Member

I could be mistaken, but this sounds like a misunderstanding of how the brute force protection works.

My understanding of the functionality, is that if an account receives X invalid login attempts within a certain time frame all login attempts will be blocked until the login_lock_time_period expires. Once that time frame has passed, logins will be allowed as usual unless the bad login count is exceeded again. This process can repeat infinitely, and mainly acts as a way to slow down brute force attempts to the point of in-feasibility, while not overly burdening users.

Extending a lockout period if additional bad requests are received during the lockout is not an intended functionality, as far as I'm aware. The hook that returns if it's present on line 68 is intentional, as the lock_expires_at field will be made nil once a lock expires, using the callback on line 121.

@futuretap
Copy link
Author

You misunderstood the point. After the initial login_lock_time_period expires, an infinite number of brute force attempts are allowed without ever considering another login_lock_time_period.

This effectively disables the brute force protection after the first login_lock_time_period has passed. It is only enabled back again after a successful login (at which point, the lock_expires_at field is set back to nil).

This is clearly a severe vulnerability.

@joshbuker
Copy link
Member

joshbuker commented May 2, 2020

@futuretap

base.sorcery_config.before_authenticate << :prevent_locked_user_login

Inside def prevent_locked_user_login:

Lines 120-123:

if !login_unlocked? && config.login_lock_time_period != 0
  login_unlock! if send(config.lock_expires_at_attribute_name) <= Time.now.in_time_zone
end

return false, :locked unless login_unlocked?

And inside Sorcery's authenticate method:
@sorcery_config.before_authenticate.each do |callback|

From what I can tell, we add a callback that gets called every authentication regardless of if the login is valid, which rejects a login if the account is locked.

Can you please review those spots of the code, and help me understand where the brute force protection is getting disabled after the first lockout period?

Edit: The ordering in the authenticate method will cause an authentication to fail because of an invalid password before it fails of a locked account, but it will still prevent a successful login while locked out. This might cause some bad logging for applications that log the error returned by the authenticate method, but it would not let attackers into a locked account. The error message for a bad login shouldn't be communicated to the end user for obvious security implications, so that shouldn't be a concern either, as far as I can tell.

Edit 2: That would be the case if it still ran the callbacks, but because it returns early the callback to unlock indeed only gets run after a successful login. Issue confirmed, I'll create security releases and deploy today.

joshbuker added a commit to joshbuker/sorcery that referenced this issue May 2, 2020
The authenticate method previously would return before callbacks executed if an
invalid password was provided, which causes the brute force protection to only
work for the first lockout period, and only resets after a successful login.

Fixes Sorcery#231
@joshbuker joshbuker self-assigned this May 2, 2020
joshbuker added a commit that referenced this issue May 2, 2020
The authenticate method previously would return before callbacks executed if an
invalid password was provided, which causes the brute force protection to only
work for the first lockout period, and only resets after a successful login.

Fixes #231
@joshbuker
Copy link
Member

@futuretap A security patch has been released (v0.15.0), and a related security adversary is currently being processed by Github. Thank you for pointing this out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants