Насколько безопасна моя система входа в PHP? - PullRequest
39 голосов
/ 01 января 2012

Я новичок в 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']) в таблице пользователей в базе данных:

id | username | password  | salt       | unrelated info...
-----------------------------------------------------------
1  | bobby    | 809a28377 | 809a28377f | ...
                fd131e5934
                180dc24e15
                bbe5f8be77
                371623ce36
                4d5b851e46

Войти:

Шаг 1: Я беру имя пользователя, введенное пользователем, и просматриваю базу данных, чтобы узнать, возвращены ли какие-либо строки. На моем сайте нет 2 пользователей, которые могут делиться одинаково username, поэтому поле username всегда имеет уникальное значение. Если мне вернули 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 и cookie active_session не установлены.

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


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

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

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

Ответы [ 6 ]

29 голосов
/ 01 января 2012

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

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

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

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

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

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

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

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

9 голосов
/ 01 января 2012

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

Так как пароль попадает из браузера на сервер? Вы не упомянули о защите от атак «человек посередине».

5 голосов
/ 12 августа 2015

Этот код ...

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

3 голосов
/ 01 января 2012

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

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

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

1 голос
/ 13 января 2019

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

1 голос
/ 12 февраля 2017

Вы должны использовать mt_rand () вместо rand ()

И вот почему:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...