Предотвращение попыток быстрого входа в систему с помощью 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