Перейти к содержимому
Назад к пути
Теория 3 мин чтения

5. Динамические API: сортировка и фильтры

Динамические API: Сортировка и фильтры

Динамическая сортировка (ORDER BY) в Go — это одна из самых коварных ловушек для разработчика. Мы уже знаем, что параметры защищают нас в условиях WHERE, но в SQL есть части запроса, которые невозможно параметризовать — это имена таблиц и колонок. Сам SQL-стандарт говорит: идентификатор (имя таблицы, имя колонки, направление ASC/DESC) — это часть структуры запроса, а не данные. Драйвер database/sql это знает и подставляет любой ? в позиции идентификатора как литерал — что почти всегда приводит к синтаксической ошибке или к семантически бессмысленному запросу.

Аналогия — заказ кофе по бланку. В строку «количество сахара» вы пишете цифру, бариста кладёт нужное число ложек. Но если вы попробуете в строку «название напитка» вписать «капучино, и ещё латте», бариста просто пожмёт плечами — название напитка задано отдельным выбором (имена в БД = выбор из меню), а не текстовым вводом.

Разберем, как неосторожное использование ввода пользователя в сортировках открывает двери для инъекций, которые нельзя закрыть обычными средствами. Эта тема — главная причина того, что лаба golang-go-sqli-orderby существует в реестре отдельно: gorm с .Order(userInput) уязвим по умолчанию, и большинство Go-проектов даже не подозревают об этом.


1. Механика: "Ловушка ORDER BY"

Представьте запрос в Go-коде: sortBy := c.Query("sort") (Например: name, price). query := fmt.Sprintf("SELECT id, name FROM products ORDER BY %s", sortBy) (через fmt.Sprintf).

Это типичная история «прикрутил сортировку за 5 минут, никто не заметил». Параметр приходит из query string (/products?sort=price), разработчик доверяет фронтенду (кто же будет отправлять туда мусор), и fmt.Sprintf напрямую вставляет ввод в SQL. Здесь нет даже кавычек, чтобы их «закрыть» — атакующий вводит чистый SQL, который попадает в запрос как код.

Хакер может использовать это для извлечения данных:

  • ?sort=1 AND (SELECT ASCII(SUBSTRING(password, 1, 1)) FROM users LIMIT 1) = 65 (Сортировать по первой колонке, если первая буква пароля 'A')

Изменение порядка записей на странице подскажет хакеру, сработало ли его условие. Здесь оракул — порядок строк в JSON-ответе. Если первая буква пароля 'A' (65 в ASCII), результат сортировки по «колонке 1» эквивалентен ORDER BY id — первая запись будет с минимальным id. Если буква не 'A', 1 AND ...=65 вернёт ложь, и сортировка превращается в ORDER BY 0 или ошибку → порядок другой. 256 запросов на байт пароля, классический blind extraction.


2. Почему Prepared Statements не спасают?

Если вы попробуете написать db.Query("SELECT ... ORDER BY ?", sortBy), драйвер подставит имя колонки как строку в кавычках:

  • Ожидалось: ORDER BY price
  • Получится: ORDER BY 'price'

Для базы данных это будет сортировка по константе, которая не дает никакого эффекта. Имена колонок можно подставлять только напрямую в строку запроса. Это фундаментальное ограничение SQL-парсера: идентификаторы должны быть известны на этапе подготовки плана выполнения, иначе планировщик не может построить дерево запроса. Никакой драйвер, никакой ORM, никакой PgBouncer этого не обойдёт — это часть спецификации, а не баг.

Та же логика касается LIMIT N, OFFSET M, имени таблицы (FROM ? тоже не работает), названия схемы. Везде, где SQL ждёт идентификатор или ключевое слово — параметризация физически невозможна. Защита — только whitelist.


3. Таблица работы: Безопасно vs Уязвимо

Параметр Уязвимый код (fmt) Безопасный код (Whitelist)
Синтаксис ORDER BY %s ORDER BY price (из списка)
Ввод хакера price; DROP TABLE ИГНОР (Или ошибка)
Статус 🔴 УЯЗВИМО 🟢 БЕЗОПАСНО

4. Как это выглядит в Go (Индикаторы)

Использование fmt.Sprintf или конкатенации строк внутри ORDER BY или GROUP BY практически гарантирует наличие уязвимости. Единственным надежным решением в такой ситуации является использование белых списков. Канонический паттерн в Go — мапа разрешённых значений типа map[string]string{"name":"name ASC", "price":"price ASC"}, где ключ берётся из query string, а значение — это уже жёстко заданная константа SQL-фрагмента. Поиск в мапе по ключу c.Query("sort") возвращает либо безопасный SQL, либо дефолт "id ASC" — никакой ввод пользователя не попадает в строку запроса напрямую. Это и есть «safe dynamic API»: динамика на уровне Go-логики, статика на уровне SQL.


Динамический SQL требует особого внимания к безопасности. Хакеру достаточно внедрить одно логическое условие в сортировку, чтобы начать извлекать секреты из вашей базы. Драйверы и ORM не помогут — gorm.Order(userInput) дословно вставит ввод в SQL, sqlx.Select(...) параметризует только ?-плейсхолдеры. Здесь компилятор Go вам не союзник: string остаётся string, и db.Query примет любую конкатенацию.

Перейдем к изучению техники Whitelisting — единственного надежного способа защиты динамических запросов в Go.

Продолжить чтение

Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу

Когда закончишь — отметь раздел, чтобы продолжить.

🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru