UNION SQL Injection в поиске каталога
UNION-based SQL-инъекция через поиск товаров позволяет извлечь данные из таблицы пользователей.
mediumphpPro
Задача
# SQL Injection: UNION-based извлечение данных через поиск каталога
## Сценарий
Перед вами PHP-приложение интернет-магазина SecureShop. На странице каталога есть форма поиска по названию товара: введённая строка попадает в условие `LIKE` SQL-запроса, и сервер возвращает страницу со списком найденных товаров.
Реализация имеет ту же фундаментальную ошибку, что и в других задачах класса SQLi: значение из формы попадает в SQL-запрос **через строковую конкатенацию текста запроса**, без отделения данных от кода. Атакующий может управлять структурой запроса через содержимое поискового поля.
Особенность этой задачи — обработчик **прямо отображает** результат запроса в HTML-ответе (карточки найденных товаров): атакующему доступен **прямой канал** вывода данных из БД. Это существенно мощнее blind-вариантов: один payload — и любые данные из БД попадают на страницу. Классическая техника для этого — объединение результатов через `UNION` оператор: к исходному запросу добавляется второй запрос, выбирающий нужные данные из любой другой таблицы; результаты обоих запросов склеиваются в одну выборку, и нужные данные приходят в ответе как «дополнительные товары» на странице каталога.
В таблице пользователей хранятся логины и хеши паролей (включая `admin`), и у `admin`'а в каталоге есть необычный товар «CTF Flag», который дешёвый аккаунт `demo` купить не может — баланса не хватит.
## Что нужно понять
- Каталог рендерит **массив строк** из БД в виде HTML-карточек. Если в массив попадут строки из другой таблицы, они тоже отрисуются как «карточки».
- Поисковая форма вызывает обработчик каталога с параметром `q`. Этот параметр становится **частью SQL-кода**, а не данными — это и есть точка инъекции.
- SQLite (СУБД лабы) поддерживает объединение выборок через оператор объединения двух SELECT'ов; число столбцов в обоих SELECT'ах должно совпадать.
## Уязвимый паттерн
```php
$search = $_GET['q'] ?? '';
if ($search !== '') {
// Конкатенация значения параметра q в текст SQL — данные становятся частью кода:
$sql = "SELECT id, name, description, price, image FROM products WHERE name LIKE '%" . $search . "%'";
$stmt = $db->query($sql);
} else {
$stmt = $db->query('SELECT id, name, description, price, image FROM products');
}
$products = $stmt->fetchAll();
// далее $products рендерится в шаблоне как массив HTML-карточек товаров
```
Значение `$_GET['q']` склеивается с фрагментом SQL `LIKE '%...%'`. Парсер БД получает уже склеенную строку и не отличает «искомое имя товара» от управляющих SQL-конструкций. Атакующий, контролирующий параметр `q`, может закрыть строковый литерал кавычкой, приписать `UNION SELECT ...` и «дополнить» выборку строками из любой другой таблицы — те уйдут в `$products` и отрисуются как обычные карточки.
## Цели
1. Подтвердите, что в форме поиска значения попадают в SQL **как часть синтаксиса**. Соберите классический payload объединения в два шага: сначала определите количество колонок в исходном запросе (через `ORDER BY` с возрастающим номером — пока не получите ошибку), затем подберите типы колонок, чтобы объединение не падало.
2. Через инъекцию извлеките хеш пароля пользователя `admin` из таблицы пользователей: значение придёт прямо на страницу каталога в виде «дополнительных» карточек товара.
3. Подберите пароль `admin`'а по хешу (или используйте известный — он словарный, см. подсказки).
4. Войдите как `admin`; купите «CTF Flag»; заберите CTF-флаг со страницы профиля.
## Данные
| Параметр | Значение |
|----------|----------|
| URL приложения | Доступен в интерфейсе лабы |
| Обычный пользователь | `demo` / `demo` |
| Точка входа | поле `q` в строке URL `/catalog` |
| СУБД | SQLite |
| Поле для редактирования (Fix Mode) | `internal/handlers/handlers.php` |