Предотвращение попыток быстрого входа в систему с помощью Rack::Attack

Мы читали Полное руководство по проверке подлинности веб-сайтов на основе форм с целью предотвращения попыток быстрого входа в систему.

Одним из примеров этого может быть:

  • 1 неудачная попытка = нет задержки
  • 2 неудачные попытки = задержка 2 с
  • 3 неудачные попытки = задержка 4 сек
  • так далее

Другие способы появляются в руководстве, но все они требуют хранилища, способного записывать предыдущие неудачные попытки.

Список блогов обсуждается в одном из постов в этом выпуске (появляется под старым черным списком имен, который был изменен в документации на блочные списки) как возможное решение.

В частности, согласно Rack::Attack, одним наивным примером реализации может быть:

Где не удается войти в систему:

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

В стойку-атаку.рб:

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

Здесь есть две части, которые возвращают ответ, если он находится в блок-листе, и проверяют, не произошла ли предыдущая неудачная попытка (StorageMechanism).

Первая часть, возвращающая ответ, может быть обработана самоцветом автоматически. Тем не менее, я не вижу такой ясности во второй части, по крайней мере с де-факто выбором для кеш-бэкэнда для мира гемов и Rails, Redis.

Насколько я знаю, просроченные ключи в Redis автоматически удаляются. Это сделало бы невозможным доступ к информации (даже если срок ее действия истек), установите новое значение для счетчика и, соответственно, увеличьте время ожидания для рефрактерного периода.

Есть ли способ добиться этого с Redis и Rack::Attack?

Я думал, что, возможно, "StorageMechanism" должен оставаться абсолютно агностиком в этом случае и ничего не знать о Rack::Attack и его вариантах хранения.

1 ответ

Решение

Извините за задержку, чтобы вернуться к вам; мне понадобилось время, чтобы выкопать мой старый код, относящийся к этому.

Как обсуждалось в комментариях выше, вот решение с использованием 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 Test Suite) приложения, вы можете легко выйти за указанные пределы и непреднамеренно задушить себя. Этого можно избежать, добавив следующую конфигурацию в инициализатор:

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

Самая распространенная атака методом "грубой силы" - это атака методом "грубой силы", когда злоумышленник просто пробует большое количество писем и паролей, чтобы проверить, совпадают ли какие-либо учетные данные.

Вы должны уменьшить это в приложении, используя блокировку учетной записи после нескольких неудачных попыток входа в систему. (Например, если вы используете devise тогда есть встроенный Lockable модуль, который вы можете использовать.)

Однако такой подход к блокировке учетной записи открывает новый вектор атаки: злоумышленник может спамить систему с помощью попыток входа в систему, используя действительные адреса электронной почты и неправильные пароли, чтобы постоянно блокировать все учетные записи!

Эта конфигурация помогает уменьшить этот вектор атаки, экспоненциально ограничивая количество попыток входа с данного IP.

Я также добавил следующий дроссель запроса "catch-all":

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

Это прежде всего для подавления злонамеренных / плохо настроенных скребков; чтобы они не перегружали весь процессор сервера приложений.

Примечание. Если вы обслуживаете ресурсы через стойку, эти запросы могут учитываться при атаке в стойку, и этот дроссель может быть активирован слишком быстро. Если это так, включите условие, чтобы исключить их из отслеживания.


Я также написал интеграционный тест, чтобы убедиться, что мой 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
Другие вопросы по тегам