fire login attempts with Rack::Attack

We have been reading the Definitive guide to form based website authentication with the intention of preventing rapid-fire login attempts.

One example of this could be:

  • 1 failed attempt = no delay
  • 2 failed attempts = 2 sec delay
  • 3 failed attempts = 4 sec delay
  • etc
  • Other methods appear in the guide, but they all require a storage capable of recording previous failed attempts.

    Blocklisting is discussed in one of the posts in this issue (appears under the old name blacklisting that was changed in the documentation to blocklisting) as a possible solution.

    As per Rack::Attack specifically, one naive example of implementation could be:

    Where the login fails:

    StorageMechanism.increment("bad-login/#{req.ip")
    

    In the rack-attack.rb:

    Rack::Attack.blacklist('bad-logins') { |req|
        StorageMechanism.get("bad-login/#{req.ip}")
    }
    

    There are two parts here, returning the response if it is blocklisted and check if a previous failed attempt happened (StorageMechanism).

    The first part, returning the response, can be handled automatically by the gem. However, I don't see so clear the second part, at least with the de-facto choice for cache backend for the gem and Rails world, Redis.

    As far as I know, the expired keys in Redis are automatically removed. That would make impossible to access the information (even if expired), set a new value for the counter and increment accordingly the timeout for the refractory period.

    Is there any way to achieve this with Redis and Rack::Attack?

    I was thinking that maybe the 'StorageMechanism' has to remain absolutely agnostic in this case and know nothing about Rack::Attack and its storage choices.


    Sorry for the delay in getting back to you; it took me a while to dig out my old code relating to this.

    As discussed in the comments above, here is a solution using a blacklist , with a findtime

    # config/initilizers/rack-attack.rb
    class Rack::Attack
      (1..6).each do |level| 
        blocklist("allow2ban login scrapers - level #{level}") do |req| 
          Allow2Ban.filter( 
            req.ip, 
            maxretry: (20 * level), 
            findtime: (8**level).seconds, 
            bantime: (8**level).seconds 
          ) do 
            req.path == '/users/sign_in' && req.post? 
          end 
        end 
      end
    end
    

    You may wish to tweak those numbers as desired for your particular application; the figures above are only what I decided as 'sensible' for my particular application - they do not come from any official standard.

    One issue with using the above that when developing/testing (eg your rspec test suite) the application, you can easily hit the above limits and inadvertently throttle yourself. This can be avoided by adding the following config to the initializer:

    safelist('allow from localhost') do |req|
      '127.0.0.1' == req.ip || '::1' == req.ip
    end
    

    The most common brute-force login attack is a brute-force password attack where an attacker simply tries a large number of emails and passwords to see if any credentials match.

    You should mitigate this in the application by use of an account LOCK after a few failed login attempts. (For example, if using devise then there is a built-in Lockable module that you can make use of.)

    However, this account-locking approach opens a new attack vector: An attacker can spam the system with login attempts, using valid emails and incorrect passwords, to continuously re-lock all accounts!

    This configuration helps mitigate that attack vector, by exponentially limiting the number of sign-in attempts from a given IP.

    I also added the following "catch-all" request throttle:

    throttle('req/ip', limit: 300, period: 5.minutes, &:ip)
    

    This is primarily to throttle malicious/poorly configured scrapers; to prevent them from hogging all of the app server's CPU.

    Note: If you're serving assets through rack, those requests may be counted by rack-attack and this throttle may be activated too quickly. If so, enable the condition to exclude them from tracking.


    I also wrote an integration test to ensure that my Rack::Attack configuration was doing its job. There were a few challenges in making this test work, so I'll let the code+comments speak for itself:

    class Rack::AttackTest < ActionDispatch::IntegrationTest 
      setup do 
        # Prevent subtle timing issues (==> intermittant test failures) 
        # when the HTTP requests span across multiple seconds 
        # by FREEZING TIME(!!) for the duration of the test 
        travel_to(Time.now) 
    
        @removed_safelist = Rack::Attack.safelists.delete('allow from localhost') 
        # Clear the Rack::Attack cache, to prevent test failure when 
        # running multiple times in quick succession. 
        # 
        # First, un-ban localhost, in case it is already banned after a previous test: 
        (1..6).each do |level| 
          Rack::Attack::Allow2Ban.reset('127.0.0.1', findtime: (8**level).seconds) 
        end 
        # Then, clear the 300-request rate limiter cache: 
        Rack::Attack.cache.delete("#{Time.now.to_i / 5.minutes}:req/ip:127.0.0.1") 
      end 
    
      teardown do 
        travel_back # Un-freeze time 
        Rack::Attack.safelists['allow from localhost'] = @removed_safelist 
      end 
    
      test 'should block access on 20th successive /users/sign_in attempt' do 
        19.times do |i| 
          post user_session_url 
          assert_response :success, "was not even allowed to TRY to login on attempt number #{i + 1}" 
        end 
    
        # For DOS protection: Don't even let the user TRY to login; they're going way too fast. 
        # Rack::Attack returns 403 for blocklists by default, but this can be reconfigured: 
        # https://github.com/kickstarter/rack-attack/blob/master/README.md#responses 
        post user_session_url 
        assert_response :forbidden, 'login access should be blocked upon 20 successive attempts' 
      end 
    end
    
    链接地址: http://www.djcxy.com/p/21620.html

    上一篇: ReCAPTCHA如何工作?

    下一篇: 使用Rack :: Attack进行消防登录尝试