Защита пароля от bruteforce атаки

Я всегда хотел, чтобы хакер не смог взломать чужой пароль на сайте.
Например, если пользователь хвастается, что его пароль состоит только из цифр, то вскоре пользователь потеряет свою учетную запись.Но что, если пользователь по телефону сообщил жене свой пароль, и хакер его услышал?Что?! Знает ли хакер пароль? Все это провал. Можете ли вы помочь этому пользователю затруднить захват его учетной записи? Меня всегда волновал этот вопрос, и я думаю, что нашел способ сделать это. Или заново открыл ее, как это часто бывает. Ведь все придумано задолго до нас.Вот как будет выглядеть защита пароля.

Защита пароля от bruteforce атаки

Вводная

 

  • Пользователь хочет на сайте иметь пароль «12345».
  • Хакер может легко подобрать этот пароль.
  • Но пользователь должен войти, а хакер нет. Даже если логин и пароль хакеру известны.
  • и никаких SMS с секретными кодами и посредниками в виде дополнительных сервисов. Только пользователь и ваш сайт со страницей логина.
  • а еще можно будет сравнительно безопасно в троллейбусе сказать своей жене: «Галя, я на сайте site для нашего логина alice поменял пароль на 123456 — говорят, он более популярный, чем наш 12345». И не бояться, что аккаунт взломают за секунду.

Как работает метод? Вся конкретика — под катом.

Что потребуется?

 

  • концепт объясняет только метод аутентификации
  • реализация требует хранить только «имя пользователя«, «пароль«, «соль1» и «соль2«. Да, две соли.
  • обойдемся без таблиц логирования и счетчиков в redis
  • не будем вести таблицы с IP-адресами
  • никак не используем SMS
  • не будем блокировать попытки входа в систему. Как известно из моей прошлой неуспешной попытки, бесполезно блокировать вход — даже если хакер упрется в ограничение по времени, он просто начнет подбирать пароли сразу у нескольких пользователей. Кроме того, от ограничений пострадает и сам пользователь. Не звонить же ему в поддержку, чтобы авторизоваться на вашем сайте с прикольными картинками?
  • пользователь может поменять пароль в любое время и сделать его недействительным на остальных устройствах. Это обычное правило, но мне кажется его стоит упомянуть.
  • можно сделать процесс подбора пароля по словарю более тяжелым для хакера (опционально, будет упомянуто ниже).

Суть метода

Разрешите пользователю иметь пароль «12345», и взлом этого пароля должен быть затруднен. Например, как угадать пароль, похожий на хэш.

Как?

Представьте, если бы в браузере всегда была уникальная соль, которой можно было солить пароль. Каждому пользователю по соли. Зачем она нужна? Чтобы шифровать. Например, если зашифровать строку «12345» с солью «saltsalt» в argon2id, то получится «$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg». Поменяй соль — и хэш будет другим. Один алгоритм зашифрует одинаковые пароли по-разному, если использовать разную соль для каждого. Годится.

Но где взять эту соль? Да, вот она сидит перед монитором. Пусть выдавит еще два-три символа и наконец-то по человечески подключится. Ну, пусть будет cat. Что такое cat? Это наше секретное слово. Мы отправим его на сервер во время регистрации, и он будет генерировать соль для этого слова. А потом он пришлет нам эту соль. Вот и все — в браузере есть соль. Теперь пароль. А еще мы шифруем пароль и лаунджи солью, которую прислал сервер.

Теперь мы не шлем «12345». Мы шлем хэш, и так-как у каждого пользователя своя соль, хэш получается разный.

Кажется, брутфорсу сейчас поплохеет: мало того, что придется делать дополнительные вычисления и перебирать длинные строки хэшей аргона вместо простых цифр, так еще и у каждого пользователя будет свой хэш — теперь бесполезно пробовать одну и ту же строку в виде пароля для проверки ее у всех пользователей. Допустим, три пользователя выбрали один и тот же пароль: 12345. Но хэш у них получится разный. Потому что у каждого разная соль.

  • Браузер должен вычислять хэш пароля используя соль, которую ему ранее прислал сервер. Отправлять он должен хэш, а не сам пароль.
  • Сервер присылает соль по секретному слову, которое известно только пользователю. Оно может быть простым. Например — «cat».
  • У каждого пользователя должна быть своя соль.
  • Два пользователя, выбравшие одинаковое секретное слово, должны иметь разную соль.
  • Сервер не должен сообщать было ли использовано правильное секретное слово и верна ли соль для этого пользователя — иначе это будет подбор двух простых паролей вместо одного.
  • Если пользователь меняет секретное слово, меняется и соль.

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

  • зашел на сайт
  • ввел логин и секретное слово
  • ввел пароль
  • готово

Пароль и секретное слово могут быть очень простыми. Один или два символа. Например, пароль 12345 и секретное слово 42. И если кто-то еще придумает секретное слово 42, то это будет не страшно.

Как это работает. Пошаговый концепт

У нас есть следующие элементы:

  • веб-сервер
  • база данных и таблица users:
    • login
    • password_hash
    • salt_unique_for_each_user
    • salt_for_password
  • браузер пользователя
  • браузер хакера
  • страницы логина и регистрации на сайте
  • скрипт, который перехватывает событие submit для формы логина

Далее нам понадобятся два разных алгоритма, которые могут быть реализованы даже на одной шифровальной системе просто с разными параметрами:

  • ALG1 — асимметричный алгоритм шифрования, который генерирует хэш из строки и соли. ALG1(str, salt) = hash1. Этот алгоритм используется только на сервере.
  • ALG2 — асимметричный алгоритм шифрования, который генерирует хэш из строки и соли. ALG2(str, salt) = hash2. Этот алгоритм используется публично и должна быть возможность его реализации на клиенте (в нашем примере на javascript).

Кроме того, нам понадобится еще два алгоритма попроще:

  • ALG_SALT — алгоритм, который вычисляет случайную соль в виде строки символов. ALG_SALT() = salt. Этот алгоритм используется только на сервере.
  • ALG_PASS — алгоритм, который генерирует случайный простой пароль. ALG_PASS() = pass. Этот алгоритм используется только на сервере.

События пошагово

 

  • Пользователь переходит на страницу регистрации, так-как у него пока нет логина.
  • Сервер показывает форму с двумя полями: логин + простое секретное слово.
  • Пользователь выбирает логин — alice
  • Теперь наш пользователь выбирает секретное слово — cat
  • Пользователь нажимает кнопку “Отправить”.

Cервер проверяет и удостоверяется, что пользователь alice отсутствует в БД.

Сервер вычисляет следующие значения:

$salt_unique_for_each_user = ALG_SALT(); // строка "saltsalt"$salt_for_password = ALG1("cat", $salt_unique_for_each_user); // строка "$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"$user_simple_password = ALG_PASS(); // строка "12345"$user_simple_password_hashed = ALG2($user_simple_password , $salt_for_password); // строка "$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ"

Сервер создает в таблице пользователей запись и сохраняет данные:

INSERT INTO `users`
(
login,
password_hashed,
salt_unique_for_each_user,
salt_for_password
)
VALUES
(
"alice",
"$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ",
"saltsalt",
"$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
).

Наш сервер показывает пользователю страницу успеха регистрации с сообщением: «Пользователь alice успешно создан. Используйте временный пароль 12345 для входа.»

Пользователь радостно кричит: “Ура, я зарегистрировался на сайте site под ником alice и мне дали пароль 12345. Какой смешной и простой пароль!“. Но у квартиры пользователя очень плохая звукоизоляция, и его хакер-сосед все услышал.

  • Хакер вбивает адрес сайта в своем браузере.
  • Браузер хакера отсылает пустые куки.
  • Сервер проверяет запрос хакера — есть ли кука “salt”. Не находит ее.
  • Прежде, чем хакер пришлет украденные логин и пароль, браузер должен знать соль, чтобы ей зашифровать пароль.
  • Браузер хакера пока не хранит соль в куке «salt».
  • Сервер присылает форму логина с двумя полями: логин + секретное слово, чтобы дать пользователю возможность получить соль.

Хакер озадачен. Пока оставим его.

  • Пользователь возвращается на страницу логина.
  • Браузер пользователя отсылает пустые куки.
  • Сервер проверяет запрос пользователя — есть ли кука «salt». Не находит ее.
  • Прежде, чем пользователь пришлет логин и пароль, браузер должен знать соль, чтобы ей зашифровать пароль.
  • Браузер пользователя пока не хранит соль в куке «salt».
  • Сервер присылает форму логина с двумя полями: логин + секретное слово, чтобы дать пользователю возможность получить соль.
  • Пользователь вводит login — alice, secret — cat и нажимает кнопку «Отправить«.

Теперь сервер получает запрос и видит, что вместо пароля прислали секретное слово.

  • Сервер выбирает запись из базы данных с логином — alice и берет значения `salt_unique_for_each_user` -> $db_salt_unique_for_each_user и `salt_for_password -> $db_salt_for_password`.
  • Сервер делает вычисления схожие с теми, что он делал при регистрации. Вычисляет значение: $salt_for_password = ALG1(«cat», $db_salt_unique_for_each_user).
  • Сервер отсылает значение соли $salt_for_password в ответе пользователю. Эта соль правильная. Если с ее помощью зашифровать пароль 12345, получится хэш, который сейчас хранится в БД. В заголовках ответа от сервера указано — `установить куку salt = $db_salt_for_password`. Также давайте сохраним и логин: `установить куку login = «alice»`.

ПояснениеСервер никак не уведомляет какая соль была отправлена — правильная или нет. Результат ее использования будет ясен, когда с ней попытаются авторизоваться с правильными логином и паролем.

  • Пользователь получает ответ сервера. Его страница либо перегружается, либо сразу динамически меняется.
  • Браузер пользователя отсылает куки: login = alice, salt = "$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg".
  • Сервер проверяет запрос пользователя — есть ли кука “salt”. Находит ее.
  • Браузер уже имеет соль, чтобы ей зашифровать пароль.
  • Сервер присылает форму логина с двумя полями: логин (уже имеет значение alice) + пароль.
  • Пользователь вводит свой простой пароль 12345 и нажимает кнопку «Отправить«.
  • Браузер перехватывает событие onSubmit.
  • Вычисляет $password_hashed = ALG2(«12345», "$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg").
  • Отправляет данные «alice»/$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ, а сам пароль 12345 никуда не шлет.

Сервер получает запрос на аутентификацию:

  • Данные логин+пароль: «alice»/$password_hashed
  • Идет в БД, достает значение `password_hashed` -> $db_password_hashed.
  • Сравнивает $db_password_hashed === $password_hashed?
  • Хэши совпадают, авторизация успешна.

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

Тем временем наш хакер решает проверить эту странную форму входа:

  • Хакер вводит login — alice, secret — dog и нажимает кнопку «Отправить«.
  • Сервер получает запрос хакера и видит, что вместо пароля прислали секретное слово.
  • Сервер выбирает запись из базы данных с логином — alice и берет значения `salt_unique_for_each_user` -> $db_salt_unique_for_each_user и `salt_for_password` -> $salt_for_password.

 

  • Далее сервер вычисляет значение соли и выдает ее, но она неправильная, потому что кодовое слово чужое: $result_fake_salt = ALG1(«dog», $db_salt_unique_for_each_user). Впрочем, сервер об этом тактично умалчивает.

Сервер отсылает вычисленное значение соли обратно в браузер пользователя. В заголовках указано — `установить куку salt = $result_fake_salt`. Также сохраняется и логин: `установить куку login = «alice»`.

ПояснениеЧтобы помочь хакеру в деле нелегкого труда, сервер отправляет ему соль. Но определить со стороны: правильное ли было секретное слово или нет — невозможно.

  • Хакер получает ответ сервера. Его страница либо перегружается, либо сразу динамически меняется.
  • Браузер хакера отсылает куки: login = alice, salt = $result_fake_salt.
  • Сервер проверяет запрос пользователя — есть ли кука «salt». Находит ее.
  • Браузер хакера уже имеет соль, чтобы ей зашифровать пароль.
  • Сервер присылает форму логина с двумя полями: логин (уже имеет значение alice) + пароль.
  • Хакер вводит украденный простой пароль 12345 и нажимает кнопку «Отправить«.
  • Браузер перехватывает событие onSubmit.
  • Вычисляет $password_hashed = ALG2(«12345», $result_fake_salt).
  • Отправляет данные «alice»/$password_hashed.

Сервер получает запрос на аутентификацию — «alice»/$password_hashed.
Идет в БД, достает значение `password_hashed` -> $db_password_hashed.
Сравнивает: $password_hashed === $db_password_hashed? Nope.

Хэши этих изначально одинаковых паролей не совпадают. Потому что их солили по-разному.

Хакер не сдается и идет регистрировать другого пользователя на сайте.

Совершенно случайно он вводит то же самое секретное слово, что и пользователь за стеной — cat.

Хакер получает валидную соль для пароля к новому аккаунту, и пробует ее подставить в скрипт для хэширования.

К счастью, при генерации соли пароля использовалась вторая соль (`salt_unique_for_each_user`), которая генерируется по-новому для каждого пользователя. Таким образом, у разных пользователей, даже с одними и теми же паролями и, что наиболее важно, секретными словами, будут разные соли. И соль пользователя с тем же секретным словом не будет соответствовать соли другого пользователя. И сопоставление пароля тоже не будет проблемой.

Теперь, что касается усложнения перебора паролей по словарю. Если мы модифицируем ALG2, который является общим и для сервера и для клиента, и сделаем его трудозатратным, это серьезно осложнит перебор для хакера. Напомню, ALG2 это процесс получения хэша пароля, который отправляется на сервер. На сервере этот хэш уже вычислен и хранится в БД:

  • сервер будет выполнять операцию ALG2 только один раз при записи пароля в БД или смене пароля на новый
  • клиент будет выполнять операцию ALG2 только во время аутентификации (которую нужно не путать с авторизацией). Допустим, клиент ошибся пару раз при вводе пароля — это не страшно.
  • Хакер будет делать это постоянно для каждого пароля, с чем его и можно будет поздравить. Особенно цинично, что будут затрачиваться титанические усилия на пароли типа 123/1234/12345.

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

Завершу описание концепта бочкой дегтя:

  • Если пользователь случайно неправильно введет секретное слово, он попадет в ситуацию, когда он не сможет войти по своему паролю. Придется сбросить секретное слово (в нашем случае удалить куки) и послать запрос заново. Это можно реализовать прозрачно по нажатию одной кнопки, но до этого пользователь должен еще догадаться. Можно сбрасывать принудительно при 5 неправильных попытках входа.
  • Два пользователя на одном компьютере вынуждены будут постоянно сбрасывать соль друг друга.
  • Два разных компьютера будут получать одну и ту же соль для пароля
  • Если соль сменится на сервере через один компьютер, другой компьютер со старой солью не будет знать, что ее нужно поменять
  • Можно украсть соль с компьютера и с ее помощью осуществить очень быструю атаку на аккаунт, зная что пароль очень простой.

… и ложкой меда:

  • пользователь может иметь несколько секретных слов для выполнения различных задач. Например, «cat» это зайти на почту, а «termorectal» — это показать фейковую страницу с ничем не примечательными письмами. Конечно, два пароля можно организовать в любой системе. Но второй пароль должен быть таким же сложным, как и первый. Здесь же можно помнить два простых пароля любого вида, используя удобочитаемые слова.
  • Возможна интеграция секретного слова в уже существующие системы аутентификации. Если у пользователя значение в БД `salt_for_password` не пустое, значит, что пользователь придумал секретное слово, и можно применять новый метод аутентификации. В противном случае использовать старый аутентификатор.

Возможно, вы думаете, что атаки методом перебора вас не беспокоят, потому что ваш небольшой и не очень популярный сайт не интересует хакеров. Мы обязаны сообщить вам, что это уже не так. Любой ресурс в сети подвергается многочисленным постоянным атакам. Целью таких атак является не сам сайт, а ресурсы сервера, на котором он размещен: взломанный сайт превращается в сайт для размещения вирусов, рассылки спама, фишинга, майнинга криптовалюты и т. Д. Следовательно, защита админ-панели любого сайта является обязательной. В противном случае рано или поздно доступ будет скомпрометирован.

 


Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.