2

我们一直在阅读基于表单的网站身份验证的权威指南,目的是防止快速登录尝试。

这方面的一个例子可能是:

  • 1 次尝试失败 = 无延迟
  • 2 次失败尝试 = 2 秒延迟
  • 3 次失败尝试 = 4 秒延迟
  • ETC

其他方法出现在指南中,但它们都需要能够记录以前失败尝试的存储。

在本期的一篇文章中讨论了阻止列表(出现在文档中更改为阻止列表的旧名称黑名单下)作为一种可能的解决方案。

具体而言,根据 Rack::Attack,一个简单的实现示例可能是:

登录失败的地方:

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

在 rack-attack.rb 中:

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

这里有两部分,如果它被列入黑名单,则返回响应,并检查是否发生了先前的失败尝试(StorageMechanism)。

第一部分,返回响应,可以由 gem 自动处理。但是,我认为第二部分不是很清楚,至少对于 gem 和 Rails 世界的缓存后端 Redis 来说是事实选择。

据我所知,Redis 中过期的键会被自动删除。这将无法访问信息(即使已过期),为计数器设置一个新值并相应地增加不应期的超时。

有没有办法通过 Redis 和 Rack::Attack 实现这一点?

我在想,在这种情况下,“StorageMechanism”可能必须保持绝对不可知论,并且对 Rack::Attack 及其存储选择一无所知。

4

1 回答 1

5

很抱歉延迟回复您;我花了一段时间来挖掘与此相关的旧代码。

正如上面评论中所讨论的,这里是一个使用 a 的解决blacklist方案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

您可能希望根据您的特定应用程序调整这些数字;上面的数字只是我认为对我的特定应用程序“合理”的数字——它们并非来自任何官方标准。

使用上述方法的一个问题是,在开发/测试(例如您的rspec测试套件)应用程序时,您可以轻松地达到上述限制并无意中限制自己。这可以通过将以下配置添加到初始化程序来避免:

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

最常见的暴力登录攻击是暴力密码攻击,攻击者只需尝试大量电子邮件和密码以查看是否有任何凭据匹配。

在几次失败的登录尝试后,您应该在应用程序中使用帐户 LOCK 来缓解这种情况。(例如,如果使用,devise则有一个内置Lockable模块可供您使用。)

然而,这种帐户锁定方法打开了一个新的攻击向量:攻击者可以通过登录尝试向系统发送垃圾邮件,使用有效的电子邮件和错误的密码,不断重新锁定所有帐户!

此配置通过以指数方式限制来自给定 IP 的登录尝试次数,有助于缓解该攻击向量。

我还添加了以下“包罗万象”的请求限制:

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

这主要是为了限制恶意/配置不当的爬虫;以防止它们占用所有应用服务器的 CPU。

注意:如果您通过机架服务资产,这些请求可能会被机架攻击计数,并且此限制可能被激活得太快。如果是这样,请启用条件以将它们排除在跟踪之外。


我还编写了一个集成测试以确保我的Rack::Attack配置正常工作。让这个测试工作有一些挑战,所以我会让代码+注释自己说话:

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
于 2017-05-20T13:22:17.120 回答