Перейти к содержимому
← Каталог php SQL Injection

Second-Order SQL Injection через профиль пользователя

Имя из БД подставляется в SQL через конкатенацию — Second-Order SQLi.

hardphpPro
Задача
# Second-Order SQL Injection через профиль пользователя ## Сценарий Вы исследуете интернет-магазин SecureShop. Магазин поддерживает регистрацию, авторизацию, корзину и каталог товаров. На странице профиля пользователя приложение отображает дополнительную секцию «Similar Users» — список пользователей с похожим полным именем, чтобы создать ощущение сообщества и помочь найти друзей. Команда разработки выполнила базовое ревью безопасности. Регистрация и логин используют параметризованные запросы — поиск инъекций на этих формах ничего не даст: специальные символы корректно сохраняются как обычная строка, INSERT защищён, а проверка пароля идёт через `password_verify()`. На первый взгляд приложение защищено. Однако в коде есть ошибка, которая является классическим примером Second-Order SQL Injection. Разработчик исходит из предположения, что данные, однажды прошедшие через параметризованный запрос при записи, становятся «доверенными» и могут безопасно использоваться в других местах. Это опасное заблуждение. ## Что такое Second-Order SQL Injection Обычная (first-order) SQL-инъекция выполняется сразу — пользователь отправляет вредоносный ввод, и тот немедленно интерпретируется как SQL. Прямые формы поиска, login bypass через одинарную кавычку с тавтологическим условием — это first-order. Second-Order SQL Injection (известна также как **stored** или **persisted SQLi**) работает в два этапа: 1. **Сохранение payload.** Атакующий отправляет SQL-payload через любое поле, которое корректно сохраняется в базу через параметризованный запрос. На этом этапе атака «спит» — никакой инъекции ещё не происходит, payload лежит в БД как обычная строка. 2. **Активация payload.** В другом месте приложения данные читаются из БД и подставляются в новый SQL-запрос через конкатенацию. Разработчик считает, что раз данные пришли «из своей БД», они безопасны, и пропускает параметризацию. На этом этапе сохранённый payload интерпретируется как SQL и инъекция срабатывает. Этот паттерн опасен тем, что: - автоматические сканеры часто пропускают такие уязвимости — они не срабатывают на месте ввода; - code review при беглом просмотре функции записи (`register`) не находит ничего подозрительного; - даже опытные разработчики ошибочно делят данные на «доверенные» (из БД) и «недоверенные» (от пользователя). ## Уязвимый паттерн В лабе живут одновременно две функции: безопасная (запись fullname через prepared statement) и уязвимая (чтение fullname и подстановка в новый SQL через конкатенацию). ```php // БЕЗОПАСНО: INSERT через prepared statement — payload сохраняется как обычная строка $stmt = $db->prepare( "INSERT INTO users (username, password, role, fullname, balance, avatar_url) VALUES (?, ?, 'user', ?, 0, '/static/img/avatar-default.svg')" ); $stmt->execute([$username, $hash, $fullname]); ``` ```php // УЯЗВИМО: чтение fullname из БД и подстановка в новый SQL-запрос через конкатенацию. // Разработчик ошибочно считает данные «из своей БД» доверенными и пропускает параметризацию. $fullname = $user['fullname']; $sql = "SELECT username, fullname FROM users WHERE fullname LIKE '%" . $fullname . "%' AND id != " . (int)$user['id']; $similarUsers = $db->query($sql)->fetchAll(); ``` Payload, переданный через форму регистрации в поле fullname, **сохраняется** в таблице `users` как обычная строка. На этапе записи атака «спит». Но когда обработчик профиля читает этот же fullname обратно и подставляет его в SQL через конкатенацию — парсер БД интерпретирует кавычки и ключевые слова в значении как SQL-синтаксис. Получается двухтактная атака: store → trigger. ## Цели 1. Зарегистрировать нового пользователя с SQL-payload, спрятанным в поле «Full Name» формы регистрации. 2. Войти под этим пользователем и открыть страницу профиля. 3. Спровоцировать срабатывание payload в секции «Similar Users» страницы профиля. 4. Извлечь чувствительные данные (логин и bcrypt-хэш пароля администратора), используя их далее для авторизации как admin. 5. Получить флаг через cabinet админа. ## Условия | Параметр | Значение | |----------|----------| | Точка входа | Регистрация — поле Full Name (`/register`) | | Точка срабатывания | Страница профиля, секция Similar Users (`/profile`) | | Обычный пользователь | `demo` / `demo` (опционально для разведки) | | Тип флага | Содержится в env-переменной `LAB_FLAG`, выдаётся на странице профиля админа после покупки товара «CTF Flag» | | Стек | PHP 8.3 + Apache + SQLite (PDO) | ## Что важно понять - INSERT в `register` использует prepared statement и **не** уязвим — нет смысла пытаться сломать регистрацию. - Login через `password_verify()` тоже безопасен — bcrypt-сравнение нельзя обмануть SQL-инъекцией. - Уязвимость находится **только** в чтении данных из БД и подстановке их в новый SQL-запрос без параметризации. - Разведка: посмотрите, какие данные отображаются на странице профиля кроме базовой информации о пользователе. Что нового появляется на этой странице по сравнению с другими?
🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru