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