Код-ревью Middle Сессии и безопасность обновлено

Код-ревью: вход и профиль на чистом PHP

Вопрос

Найдите уязвимости в обработчике входа и странице профиля.

<?php
session_start();
$pdo = new PDO('mysql:host=db;dbname=app', 'root', 'root');

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = $_POST['email'];
    $sql = "SELECT * FROM users WHERE email = '$email'";
    $user = $pdo->query($sql)->fetch();
    if ($user && md5($_POST['password']) === $user['password']) {
        $_SESSION['user_id'] = $user['id'];
        header('Location: /profile.php?id=' . $user['id']);
        exit;
    }
    echo "Неверный пароль для " . $email;
}

// profile.php
$id = $_GET['id'];
$profile = $pdo->query("SELECT * FROM users WHERE id = $id")->fetch();
echo "<h1>Привет, " . $profile['name'] . "</h1>";
echo "<a href='" . $profile['website'] . "'>Сайт</a>";
$prefs = unserialize($_COOKIE['prefs']);

Коротко

Почти каждая строка здесь уязвима.

Что должен найти ревьюер

  1. Безопасностьстрока 3
    Учётные данные БД в коде и пользователь root: секреты должны быть в переменных окружения, а у приложения минимальные права.
  2. Безопасностьстрока 7
    SQL-инъекция: email подставляется прямо в запрос. Нужен prepare с параметром.
  3. Безопасностьстрока 9
    Пароль хранится как MD5 без соли и сравнивается небезопасно. Нужны password_hash и password_verify.
  4. Безопасностьстрока 10
    После входа не вызывается session_regenerate_id(true): возможна фиксация сессии.
  5. Безопасностьстрока 5
    Нет CSRF-токена и ограничения попыток входа: перебор паролей и подделка запроса.
  6. Безопасностьстрока 14
    XSS: email из запроса выводится без htmlspecialchars. Ещё сообщение подтверждает существование адреса.
  7. Безопасностьстрока 19
    IDOR и SQL-инъекция: профиль берётся по id из адреса без проверки, что это текущий пользователь, и без параметризации.
  8. Безопасностьстрока 20
    Хранимый XSS: имя выводится без экранирования.
  9. Безопасностьстрока 21
    Ссылка из профиля без проверки схемы: javascript: в href выполнит код. Нужна проверка https?:// и экранирование атрибута.
  10. Безопасностьстрока 22
    unserialize данных из cookie: PHP object injection. Для внешних данных json_decode.
  11. Ошибкастрока 19
    Нет проверки, что профиль найден: fetch() вернёт false, и обращение к индексу даст предупреждения.

Разбор

Почти каждая строка здесь уязвима. Минимальный набор исправлений: подготовленные запросы для всех SQL, password_hash/password_verify, session_regenerate_id(true) после входа, CSRF-токен и ограничение попыток, экранирование всего вывода через htmlspecialchars(..., ENT_QUOTES, 'UTF-8'), проверка схемы ссылок, профиль только текущего пользователя ($_SESSION['user_id']), json_decode вместо unserialize, секреты в окружении.

В реальном проекте большую часть этого дают фреймворк и ORM: Eloquent или Doctrine параметризуют запросы, Blade и Twig экранируют вывод, middleware проверяет CSRF.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.