Я новичок в PHP, и это тоже моя первая система входа в систему, поэтому было бы здорово, если бы вы, ребята, могли посмотреть мой код и посмотреть, можете ли вы обнаружить любые дыры в безопасности:
примечание: я дезинфицирую все входные данные пользователя, хотя здесь это не показано.
Шаг 1: Я беру пароль, который пользователь выбрал, и запустил его через эту функцию:
encrypt($user_chosen_password, $salt); function encrypt($plain_text, $salt) { if(!$salt) { $salt = uniqid(rand(0, 1000000)); } return array( 'hash' => $salt.hash('sha512', $salt.$plain_text), 'salt' => $salt ); }
Шаг 2. Затем я сохраняю хэш и соль ( $password['hash']
и $password['salt']
) в таблице users в базе данных:
id | username | password | salt | unrelated info... ----------------------------------------------------------- 1 | bobby | 809a28377 | 809a28377f | ... fd131e5934 180dc24e15 bbe5f8be77 371623ce36 4d5b851e46
Шаг 1: Я беру имя пользователя, введенное пользователем, и просматриваю базу данных, чтобы узнать, возвращены ли какие-либо строки. На моем сайте нет 2 пользователей, которые могут использовать одно и то же имя пользователя, поэтому поле имени пользователя всегда имеет уникальное значение. Если я получу 1 строку, я возьму соль для этого пользователя.
Шаг 2. Затем я запустил введенный пользователем пароль через функцию шифрования (как ранее было выше), но на этот раз я также предоставил соль, полученную из базы данных:
encrypt($user_entered_password, $salt);
Шаг 3: теперь у меня есть правильный пароль для сопоставления в этой переменной: $password['hash']
. Поэтому я так второй раз просмотрел базу данных, чтобы увидеть, введенное ли имя пользователя и хешированный пароль вместе возвращают одну строку. Если это так, то учетные данные пользователя верны.
Шаг 4: Чтобы зарегистрировать пользователя после прохождения своих учетных данных, я генерирую случайную уникальную строку и хеширую ее:
$random_string = uniqid(rand(0, 1000000)); $session_key = hash('sha512', $random_string);
Затем я вставляю $session_key
в таблицу active_sessions
в базе данных:
user_id | key ------------------------------------------------------------ 1 | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b
Шаг 5:
Я беру незашифрованную уникальную строку, сгенерированную на последнем шаге ( $random_string
), и устанавливаю ее как значение cookie, которое я вызываю active_session
:
setcookie('active_session', $random_string, time()+3600*48, '/');
Шаг 6:
В верхней части моего header.php
есть эта проверка:
if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) { get_userinfo(); }
Функция get_userinfo()
выполняет поиск в таблице users
в базе данных и возвращает ассоциативный массив, который хранится в сеансе с именем userinfo
:
// сначала эта функция принимает значение файла cookie active_session и хеширует его для получения session_key:
hash('sha512', $random_string);
// затем он выполняет поиск в таблице active_sessions
чтобы увидеть, существует ли запись этого key
, если это позволит захватить user_id
связанную с этой записью, и использовать ее для второго поиска в таблице users
чтобы получить userinfo
:
$_SESSION['userinfo'] = array( 'user_id' => $row->user_id, 'username' => $row->username, 'dob' => $row->dob, 'country' => $row->country, 'city' => $row->city, 'zip' => $row->zip, 'email' => $row->email, 'avatar' => $row->avatar, 'account_status' => $row->account_status, 'timestamp' => $row->timestamp, );
Если сеанс userinfo
существует, я знаю, что пользователь аутентифицирован. Если он не существует, но cookie active_session
существует, тогда эта проверка в верхней части файла header.php
создаст этот сеанс.
Причина, по которой я использую cookie, а не сеансы в одиночку, – это сохранить регистрацию. Поэтому, если пользователь закрывает браузер, сеанс может исчезнуть, но файл cookie все равно будет существовать. И так как есть проверка в верхней части header.php
, сессия будет воссоздана, и пользователь может нормально функционировать как зарегистрированный пользователь.
Шаг 1: И сеанс userinfo
и active_session
cookie active_session
не установлены.
Шаг 2: Связанная запись из таблицы active_sessions
в базе данных удаляется.
Примечания. Единственная проблема, которую я вижу (и, возможно, многие другие), заключается в том, что пользователь подделывает этот active_session
cookie active_session
, создавая его самостоятельно в своем браузере. Конечно, они должны установить в качестве значения этого файла cookie строку, которая после ее зашифрования должна соответствовать записи в таблице active_sessions
откуда я буду извлекать user_id
для создания этого сеанса. Я не уверен, какие шансы на это реалистично, для пользователя (возможно, с использованием автоматизированной программы), чтобы угадать строку, которую они не знают, будет тогда зашифрована sha512 и сопоставлена с строкой в таблице active_sessions
в базе данных чтобы получить идентификатор пользователя для создания этого сеанса.
Извините за большое эссе, но так как это такая важная часть моего сайта, и из-за моей неопытности я просто хотел запустить ее более опытными разработчиками, чтобы убедиться, что это действительно безопасно.
Итак, вы видите дыры в безопасности на этом маршруте и как его можно улучшить?
Вы должны включить некоторый тайм-аут или переход на другой ресурс, чтобы предотвратить атаки грубой силы. Существует несколько способов сделать это, включая блокировку на основе IP, инкрементные таймауты и т. Д. Ни один из них никогда не остановит хакера, но они могут сделать его намного сложнее.
Еще один момент (который вы не упомянули, поэтому я не знаю вашего плана) – сообщения об ошибках. Сделайте сообщения об ошибках максимально расплывчатыми. Предоставление сообщения об ошибке типа «То, что имя пользователя существует, но пароли не совпадают», может быть полезно для конечного пользователя, но оно убивает функциональность входа. Вы просто преобразуете атаку грубой силы, которая должна взять O(n^2)
раз до O(n)
+ O(n)
. Вместо того, чтобы попробовать каждую перестановку в таблице радуги (например), хакер сначала пытается все значения имени пользователя (с заданным паролем), пока сообщение об ошибке не изменится. Затем он знает действительного пользователя и просто должен переборщить пароль.
Вдоль этих строк вы также должны убедиться, что такое же количество времени истекает, когда имя пользователя существует и не существует. Вы выполняете дополнительные процессы, когда имя пользователя действительно существует. Таким образом, время ответа будет больше, если существует имя пользователя, а не когда оно отсутствует. Невероятно опытный хакер мог запрашивать временные запросы, чтобы найти действительное имя пользователя.
Аналогичным образом, вы должны убедиться, что в дополнение к устаревшим куки-файлам также истекает таблица сеансов.
Наконец, в get_user_info()
вы должны прекратить все открытые сеансы, если есть несколько одновременных активных хостов. Удостоверьтесь, что вы тайм-аут после определенного количества бездействия (например, 30 минут).
В соответствии с тем, что упоминал @Greg Hewgill, вы не включили ни одно из следующего:
Сервер безопасен, но не имеет значения, насколько безопасно защищен ваш алгоритм, если кто-то может прочитать данные, которые обмениваются (MITM). Вы должны убедиться, что используете только протокол шифрования.
… запустить пароль, введенный пользователем, через функцию шифрования …
Итак, как пароль поступает из браузера на сервер? Вы не упомянули о защите от нападений «человек в середине».
Похоже, что созданный вами код не тестируется с помощью автоматических блоков и тестов интеграции. Это затрудняет обнаружение ошибок, которые могут быть включены в вашу реализацию с течением времени и во время работы в рабочей среде.
Это обычно приводит к проблемам безопасности, поскольку безопасность строгого и правильного выполнения и разумная обработка данных не проверена / проверена.
(Это еще один момент в списке, см. Также ответ о том, как защитить транспортный уровень, также вы не указали, как вы защищаете данные сеанса от подделки.)
Этот код …
function encrypt($plain_text, $salt) { if(!$salt) { $salt = uniqid(rand(0, 1000000)); } return array( 'hash' => $salt.hash('sha512', $salt.$plain_text), 'salt' => $salt ); }
…плохо. Используйте новый API паролей и сделайте с ним. Если вы не специалист, вы не должны пытаться создать свою собственную систему аутентификации. Им очень сложно получить право .
Чтобы зарегистрировать пользователя после прохождения своих учетных данных, я генерирую случайную уникальную строку и хешу:
Просто позвольте PHP обрабатывать сеансы . rand()
и mt_rand()
являются очень незащищенными генераторами случайных чисел.
Вы должны использовать mt_rand () вместо rand ()
Вот почему: