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

Implement exponential backoff #106

Closed
reedloden opened this issue Dec 6, 2014 · 12 comments
Closed

Implement exponential backoff #106

reedloden opened this issue Dec 6, 2014 · 12 comments

Comments

@reedloden
Copy link

It would be really awesome if Rack::Attack supported exponential backoff so as people continue to misbehave, their ban times increase (maybe up to some max time).

This article has some useful thoughts and tables about it:
https://devcentral.f5.com/articles/implementing-the-exponential-backoff-algorithm-to-thwart-dictionary-attacks

@zmillman
Copy link
Contributor

Hmm, exponential backoff would require a really tricky re-work of how the key naming arithmetic works. You can still block 90% of abusive clients by throttling on specific actions.

The example configuration will already slow dictionary attacks down to 5 requests/20 seconds, which would make an attack using the entire Oxford English Dictionary take almost 8 days (http://blog.codinghorror.com/dictionary-attacks-101/)


After some brainstorming, I realized you can actually write a pseudo-exponential backoff using the standard Rack::Attack throttles by doing something like this:

# Allows 20 requests in 8  seconds
#        40 requests in 64 seconds
#        ...
#        100 requests in 0.38 days (~250 requests/day)
(1..5).each do |level|
  throttle("logins/ip/#{level}", :limit => (20 * level), :period => (8 ** level).seconds) do |req|
    if req.path == '/login' && req.post?
      req.ip
    end
  end
end

@zmillman
Copy link
Contributor

(note that the above solution throttles on all login requests for a single ip, not just failed ones)

@zmillman
Copy link
Contributor

@zmillman
Copy link
Contributor

zmillman commented Feb 6, 2015

@reedloden - does the Advanced Configuration page solve your issue?

@reedloden
Copy link
Author

I haven't had time to test. I could close this out if it's a problem... I hope to pick this up later this month.

@zmillman
Copy link
Contributor

zmillman commented Feb 6, 2015

Haha, not a problem at all. Just wanted to check in since it had been a while 😃

@ktheory
Copy link
Collaborator

ktheory commented Mar 6, 2015

Closing this since it's covered in the advanced config docs.

Happy throttling! 🌻

@ACPK
Copy link

ACPK commented Nov 19, 2015

@reedloden - Was this ever tested?

@reedloden
Copy link
Author

@ACPK Not sure if we ever implemented it in this fashion. I will check and see.

@ACPK
Copy link

ACPK commented Nov 19, 2015

@reedloden Thanks!

@em4nue1
Copy link

em4nue1 commented Jan 8, 2020

Can anyone confirm this approach actually works? To me, it seems like only the last rule is active.

In my example below, I only have a limit after the 10th request (level = 5). It should already limit after the 2nd request within 8 seconds etc.

(1..5).each do |level|
  throttle('req/ip', limit: (2 * level), period: (8 ** level).seconds) do |req|
    req.ip
  end
end

Thank you!

@reedloden
Copy link
Author

@em4nue1 You need to have unique names for each throttle. So, use throttle("req/ip/#{level}") or similar.

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

No branches or pull requests

6 participants