Вероятно, вы использовали функцию восстановления пароля на каком-то сайте. Стандартная практика – спросить у пользователя адрес электронной почты (который вы запрашивали при регистрации на сайте) и отправить на этот адрес электронное письмо со ссылкой. Эта ссылка содержит некоторую конкретную информацию, которая позволяет приложению узнать, для какого пользователя выполняется восстановление пароля. Затем он запрашивает новый пароль, и все готово.
Если вы думаете: «Почему бы мне просто не отправить им по электронной почте пароль, который у них уже есть?», это означает, что вы вообще их не хэшируете. Сделайте это, прежде чем продолжить чтение.
Вы можете подумать: «Эй, даже я знаю, что вам не следует этого делать!», но по крайней мере в нескольких примерах в первых результатах поиска Google говорится о сохранении паролей в базе данных без хеширования. Таким образом, кажется, что это нормально и подводит нас к нашему первому запрету.
1. Не храните пароли в открытом виде в базе данных.
Хеширует ли ваш сайт пароли перед сохранением их в БД? Хорошо, поехали вперед.
Вернемся к отправке пользователям ссылки по электронной почте, чтобы они могли сбросить свой пароль. Предположим, что URL для восстановления пароля вашего сайта:
https://your.site/password_recovery.php
https://your.site/password_recovery.php
Тривиальное решение (опять же, видно из лучших результатов Google) заключаться в следующем:
https://your.site/password_recovery.php?user=john@gmail.com
Это небезопасно. Зачем? Потому что, если злоумышленник знает чей-то адрес электронной почты, он может изменить пароль. Это наш второй запрет.
2. Не используйте общедоступную информацию в качестве токена для восстановления пароля.
Вы можете подумать: «Какую еще информацию я получил от пользователя, которую я могу использовать? Может быть, user_id? »
https://your.site/password_recovery.php?user_id=13
Это тоже небезопасно. Зачем? Хотя злоумышленник, скорее всего, не может знать, у какого пользователя он меняет пароль, он может заблокировать доступ людей к их учетным записям. Кроме того, если на вашем сайте есть какие-то функции администратора, то user_id = 1, скорее всего, будет пользователем с правами администратора, и теперь вы попали в мир боли.
3. Не используйте последовательные номера идентификаторов в качестве токенов восстановления пароля.
На этом этапе вы, вероятно, поняли, что не можете напрямую использовать что-либо из базы данных. Но в попытке обойти это вы думаете: «Давайте использовать md5 хэш, это должно помешать злоумышленнику правильно угадать пользователя?».
Итак, вы попробуете что-то вроде:
$token = md5($user["email"]);
https://your.site/password_recovery.php?token=$token
Вы даже можете найти его прямо в базе данных, используя функцию md5 в mysql!
Это тоже небезопасно. Почему? Потому что, если я могу угадать, как генерируется токен, например, посмотрев на мой токен (или вы опубликовали код в github, или, скажем, я выяснил, какую из 10 лучших реализаций Google вы копируете и вставляете), тогда я могу сгенерировать токен для всех. Как сказал великий Клод Шеннон: «Системы следует проектировать, исходя из предположения, что противник немедленно ознакомится с ними полностью».
4. Не ставьте свою безопасность в зависимость от того факта, что ваш код является секретом.
Варианты из двух лучших результатов Google:
$token = md5(18247 * 2567 + $user["id"]);
$salt = "РанДомНый длинный набор символов"
$token = md5($salt.$user["email"])
Если я знаю, как построен ваш хеш, я могу создать токены восстановления для всех ваших пользователей.
5. Не создавайте токены таким способом, который может быть сгенерирован в автономном режиме кем-то, знакомым с системой.
«Но подождите!», – можете подумать вы. «Что, если мы будем использовать шифрование вместо хеширования? Я могу держать ключ в сейфе и это решит нашу проблему ”
Например:
$token = encrypt($user["email"], $some_key);
Это приносит больше проблем, чем решает.
- Вам нужно найти способ надежно хранить ключ.
- Если ключ будет скомпрометирован, и вы захотите его изменить, вы сломаете предыдущие токены.
- В зависимости от алгоритма и размера ключа, ключ может быть восстановлен, чему может способствовать тот факт, что открытый текст и алгоритм известны .
- Если ключ восстановлен (либо путем перебора, либо из-за какой-либо другой уязвимости), то злоумышленник (или ваш коллега) может сгенерировать токены сброса паролей для любого пользователя в автономном режиме.
6. Не используйте шифрование, если вы можете. Это вызывает больше проблем, чем решает проблему.
1. Создавайте токены, не зависящие от пользовательских данных.
С этого момента мы меняем модель атаки и подход генерации токенов. До сих пор злоумышленник угадывал токен, связанный с конкретным пользователем. Это больше невозможно. Все, что может сделать злоумышленник, – это вызвать сброс пароля для пользователя и угадать, какой токен был сгенерирован.
«Так как же мне создать этот токен?» – спросите вы себя. Может быть, использовать то, что PHP уже дает нам, например uniqid
:
php> echo uniqid ();
593aceadf16aa
Это должен быть случайный уникальный идентификатор, верно ?
Вывод uniqid
генерируется исключительно из текущего времени сервера. Злоумышленник контролирует, когда он запрашивает смену пароля, поэтому даже если разрешение, которое он использует, составляет до микросекунд, он, вероятно, сможет сузить диапазон до пары миллисекунд, сделав идентификатор угадываемым за пару тысяч попыток.
7. Не генерируйте токены на основе времени, их можно угадать.
Примечание. Хеширование предполагаемого токена не добавляет безопасности. Конечно, это выглядит более безопасным, но это не так. Это похоже на то, как если вы угадываете чей-то пароль, вас не волнует хэш его пароля, если вы можете просто угадать, что пользователь ленив, а его пароль буквально «пароль».
В этот момент вы могли бы сказать: «Зачем мне использовать uniqid, если мне нужно что-то случайное. Я просто воспользуюсь функциями PHP со случайными числами! »
$token = rand(); // Значение от 0 до 2147483647.
Проблема с (многими) генераторами случайных чисел в том, что они детерминированы. Если вы знаете несколько выходов, вы действительно можете знать, каким будет следующий.
И rand()
, и mt_rand()
уязвимы для такого рода атак. Менее известное lcg_value
тоже. PHP 7 представил random_bytes()
и random_int()
, которые возвращают случайные данные безопасным для приложений такого типа способом, то есть они являются криптографически безопасными генераторами случайных чисел.
8. Не используйте rand, mt_rand или lcg_value в качестве источника случайных чисел для чего-либо, связанного с безопасностью.
2: Используйте random_int или random_bytes для безопасных случайных чисел.
Так как же создать случайный и непредсказуемый токен?
Простой способ:
$length = 16;
$token = bin2hex (random_bytes ($ length)); // вывод bin2hex безопасен для URL.
Вот и все, мы закончили. У нас есть токен безопасности, который можно использовать для восстановления пароля.
Теперь вы можете приступить к реализации остальных функций. Однако есть несколько дополнительных вещей, которые вам нужно иметь в виду.
Токены для сброса не должны быть действительными вечно, поэтому вам следует добавить новый столбец рядом с токеном с датой создания и использовать его, чтобы проверить, следует ли его принять или нет. Кроме того, они должны быть одноразовыми токенами. Итак, как только пользователь сбросит свой пароль, удалите токен.
3. Установите срок жизни ваших жетонов сброса, чем короче, тем лучше. 1 час, вероятно, разумное значение по умолчанию.
4. Удалить токен после использования.
Подведем итоги
Логика сброса паролей примерно должна быть следующей:
- Пользователь запрашивает сброс пароля, указав свой адрес электронной почты.
- Найдите пользователя в базе данных, используя адрес электронной почты.
- Надежно создайте токен и сохраните его в базе данных вместе со временем его создания.
например:$token = bin2hex(random_bytes (16));
- Отправьте электронное письмо со ссылкой на страницу восстановления пароля и токеном в качестве параметра строки запроса.
- Найдите пользователя в базе данных, используя токен, если он найден и срок его действия не истек, запросите у него новый пароль
- Сохраните новый пароль в базе данных
- Удалите использованный токен из базы данных.
Не делайте этого
- Сохранять пароли в открытом виде в базе данных.
- Используйте общедоступную информацию в качестве токена восстановления пароля.
- Используйте последовательные идентификационные номера в качестве токенов восстановления пароля.
- Создавать токены таким образом, чтобы их можно было генерировать в автономном режиме.
- Используйте шифрование
- Генерируйте свои токены в зависимости от времени
- Используйте rand, mt_rand или lcg_value в качестве источника случайных чисел для всего, что связано с безопасностью