SQL Injection: Second-Order
SQL-инъекция второго порядка: payload сохраняется при регистрации и срабатывает при просмотре профиля.
hardpythonPro
Задача
# SQL Injection: уязвимость второго порядка
## Сценарий
Перед вами Flask-приложение интернет-магазина SecureShop. Это нетипичная задача для SQLi: разработчик уже знает про класс проблемы и в очевидных местах работы с пользовательским вводом сделал всё правильно. В частности, форма регистрации использует параметризованные запросы — попытаться сделать SQLi прямо там не получится.
Уязвимость находится не в **записи** пользовательских данных в БД, а в одном из мест **последующего чтения** этих данных. Где-то в обработчике, который запускается уже после того, как пользователь зарегистрирован и аутентифицирован, значение, **сохранённое в БД на этапе регистрации**, извлекается из неё и подставляется в **другой** SQL-запрос — и в этом другом запросе параметризации нет, идёт обычная строковая интерполяция. То есть данные, безопасно записанные при регистрации, при последующем чтении выходят за пределы своей роли «строка-литерал» и становятся частью SQL-синтаксиса. Это и есть классический SQLi второго порядка: payload «дремлет» в БД и срабатывает позже, в стороннем запросе.
Ключевое заблуждение разработчика, ведущее к этому классу: «данные из БД — доверенные, мы же сами их туда положили». На самом деле данные из БД — это такой же недоверенный ввод, как параметры HTTP-запроса: атакующий мог их туда положить через любую точку входа, и единственный надёжный режим работы с ними — обращаться как с произвольной строкой и пропускать через параметризацию ровно так же, как и user input.
В таблице пользователей есть привилегированная учётная запись `admin` с заказом, в котором фигурирует CTF-флаг.
## Цели
1. Найдите место в обработчике, где значение, **сохранённое в БД при регистрации пользователя**, при последующем чтении подставляется в SQL-запрос **через строковую интерполяцию** (а не через параметризацию). Подсказка: ищите не в обработчиках записи, а в обработчиках, которые читают что-то по имени текущего пользователя.
2. Зарегистрируйтесь с таким значением поля логина, чтобы при последующем чтении (в этом «втором» запросе) условие `WHERE` стало всегда истинным — то есть выборка возвращала записи всех пользователей, а не только текущего.
3. Войдите под этим аккаунтом, откройте страницу со списком заказов и заберите CTF-флаг прямо из её содержимого.
## Данные
| Параметр | Значение |
|----------|----------|
| Обычный пользователь | `demo` / `demo` |