Подтвердить что ты не робот

Насколько безопасна моя система входа в систему PHP?

Я новичок в 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 include есть эта проверка:

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 и cookie active_session не установлены.

Шаг 2: Связанная запись из таблицы active_sessions в базе данных удаляется.


Примечания. Единственная проблема, которую я вижу (и, возможно, многие другие), заключается в том, что пользователь подделывает этот active_session cookie, создавая его самостоятельно в своем браузере. Конечно, они должны установить в качестве значения cookie строку, которая после ее зашифрования должна соответствовать записи в таблице active_sessions, откуда я буду получать user_id для создания этого сеанса. Я не уверен, какие шансы на это реалистично, для пользователя (возможно, с использованием автоматизированной программы), чтобы угадать строку, которую они не знают, будет зашифрована sha512 и сопоставлена ​​с строкой в ​​таблице active_sessions в базу данных, чтобы получить идентификатор пользователя для создания этого сеанса.

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

Итак, вы видите дыры в безопасности на этом маршруте и как его можно улучшить?

4b9b3361

Ответ 1

Вы должны включить некоторый тайм-аут или переход на другой ресурс, чтобы предотвратить атаки грубой силы. Существует несколько способов сделать это, включая блокировку на основе IP, инкрементные таймауты и т.д. Ни один из них никогда не сможет остановить хакера, но они могут сделать его намного сложнее.

Еще один момент (который вы не упомянули, поэтому я не знаю вашего плана) - сообщения об ошибках. Сделайте сообщения об ошибках максимально расплывчатыми. Предоставление сообщения об ошибке типа "То, что имя пользователя существует, но пароли не совпадают", может быть полезно для конечного пользователя, но оно убивает функциональность входа. Вы просто превратили атаку грубой силы, которая должна O(n^2) занять время O(n) + O(n). Вместо того, чтобы попробовать каждую перестановку в таблице радуги (например), хакер сначала пытается все значения имени пользователя (с заданным паролем), пока сообщение об ошибке не изменится. Затем он знает действительного пользователя и просто должен переборщить пароль.

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

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

Наконец, в вызове get_user_info() вы должны прекратить все открытые сеансы, если есть несколько одновременных активных сеансов. Удостоверьтесь, что вы тайм-аут после определенного количества бездействия (например, 30 минут).

В соответствии с тем, что упоминал @Greg Hewgill, вы не включили ни одно из следующего:

  • SSL/зашифрованное соединение между сервером-клиентом
  • Другие транспортные протоколы, которые вы много используете для обработки аутентификации (например, OAuth)

Сервер безопасен, но не имеет значения, насколько безопасно ваш алгоритм, если кто-то может прочитать данные, которые обмениваются (MITM). Вы должны убедиться, что используете только протокол шифрования.

Ответ 2

... запустить введенный пользователем пароль через функцию шифрования...

Итак, как пароль поступает из браузера на сервер? Вы не упомянули о защите от нападений "человек-в-середине".

Ответ 3

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

Это обычно приводит к проблемам безопасности, поскольку безопасность строгого и правильного выполнения и правильная обработка данных не проверена/проверена.

(Это еще одна точка в списке, см. также ответ о том, как защитить транспортный уровень, также вы не указали, как вы защищаете данные сеанса от подделки.)

Ответ 4

Этот код...

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() являются очень незащищенными генераторами случайных чисел.

Ответ 6

Что касается паролей в php, вы не должны их шифровать. Вы должны их хешировать с помощью password_hash() затем при входе в систему используйте password_verify() чтобы убедиться, что пароль через html-форму соответствует сохраненному хешу в базе данных.