Объект: веб-форма входа в систему.
Дана задача: усилить защиту аккаунта пользователя от подбора простого пароля к его аккаунту, используя минимум средств.
Что такое минимум средств? Это не использовать таблицы-справочники для блокировки по IP-адресу и User-Agent. Не использовать лишние запросы к системе, не захламлять систему авторизации лишними циклами.
И, выполнить совершенно волшебное требование — даже если бот введет нужные логин и пароль… не дать ему войти, а вот реального пользователя впустить.
Можно ли так сделать? В теории, конечно, нет. Но в практике, в частном порядке и при определенных условиях, как оказалось, весьма возможно.
Приглашаю под кат за подробностями.
Итак, предположим, что, логин у нашего пользователя «test», а пароль «12345». Мерзкий бот подключил свой словарь сгенерированных паролей, и готов работать с нашим API со скоростью 700 паролей в секунду. Он знает, что логин пользователя — «test». Ситуация аховая: пароль «12345» будет вычислен за очень малое время. Пользователь, тем временем, открыл сайт и начал вводить логин и пароль в веб-форму логина.
Давайте внесем изменения в систему авторизации, пока ни один из них не начал свою работу, и не случилась беда.
Магия будет заключаться в третьей переменной, которую следует «приклеить» к паре логин-пароль. Я назвал ее touch.
Каждый раз, когда кто-то получает (внимание: получает, а не запрашивает!) логин-пароль в БД, дата touch обновляется на текущую дату-время:
login/password/touch: 'test', '12345', '2014-12-13 14:00:00'.
Предположим, что бот начал первую итерацию и спросил пароль «1» для логина «test» в '2014-12-13 15:00:00'. Cрабатывает контроллер login_check, который читает из базы данных пару логин-пароль, которую никто не «трогал» целых 2 секунды! Откуда вообще эти 2 секунды?! Об этом будет дальше.
Такая пара логин-пароль находится. Разница между последним touch и текущим временем — 1 час. Поэтому запись успешно возвращается на наш запрос.
Сначала пара логин-пароль сличается и login_check приходит к выводу, что test/12345 не равно test/1. Контроллер возвращает «auth error». При этом обновляется дата touch для этой пары логин-пароль на '2014-12-13 15:00:00'.
Бот приступает к следующей итерации: спрашивает пароль «2».
Скорость работы бота измеряется микросекундами. Он спрашивает запись сразу же: '2014-12-13 15:00:00'.
И тут вступает в действие наш алгоритм — условие по третьему параметр touch уже не выполняется. 2 секунды еще не прошли. Fail.
Модифицированный нашей логикой контроллер login_check не может получить пару логин-пароль из БД.
Запись существует, но ее дата touch еще слишком «свежая».
И она она уже не попадает в выборку. А раз такой пары логин-пароль нет, то контроллер ответит боту «auth error».
Бот не сдается, продолжает подбор и, наконец, приходит к правильному паролю «12345».
Вероятность, что именно текущая попытка вернет успех — крайне и крайне мала. 1/700 на каждую попытку входа! То есть, если раньше было 1:1, то теперь 1:700. И чем быстрее бот, тем больше вероятность, что его ждет fail.
В итоге только очень малая часть паролей будет действительно проверена. Остальные получат ложные срабатывания, даже если они будут верны.
А что пользователь?
Начнем с пользователя. Пользователь, в отличие от бота, вводит данные в веб-форму руками через клавиатуру и смотрит зрительными органами на монитор. А гибкость его алгоритмических способностей куда лучше, чем бота. По сути, пользователь в некотором роде искусственный интеллект. А значит, часть логики уже лежит в нем. И мы ею воспользуемся!
Когда пользователь видит ошибку авторизации, он часто переписывает пароль заново. Даже если пароль он только что вбил сам. Даже если пароль подставлен автоматом из password-manager. Я делал это еще до того, как применил свою систему защиты простых паролей.
Да, я обещал рассказать про две секунды. Рассказываю:
Две секунды это оптимальное время, за которое пользователь проводит операции по корректировке данных и совершает следующую попытку входа. В эти две секунды пользователь укладывается полностью. Если пользователь не уложился — он всегда может повторить попытку и за это время действие touch уже наверняка аннулируется.
В заключение.
Что будет если бот знает о 2-секундной задержке? Если применить наши тестовые данные, это значит, что эффективность бота снизится: всего 1 попытка подбора пароля вместо 1400.
P.S. Очень хочется услышать критику, потому что система уже внедрена в один проект, и пока не создала ни одного тикета с проблемой доступа к системе.
Заранее спасибо.
Автор: dso