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

4. SQL Injection внутри JSON (PostgreSQL ->>)

SQL Injection внутри JSON (Go + PostgreSQL)

Формат JSONB в PostgreSQL позволяет хранить структурированные данные и выполнять по ним эффективный поиск. Для Go-разработчика это звучит заманчиво, но именно здесь часто повторяются старые ошибки. Неправильное использование JSON-операторов при построении запросов открывает новые векторы для классических атак.

JSONB появился в PostgreSQL 9.4 (2014 год) и быстро стал популярным выбором для гибридных схем, где часть данных строго структурирована (id, created_at, status), а часть варьируется от записи к записи (settings, metadata, custom_fields). Вместо того чтобы пихать неструктурированные данные в MongoDB, Go-команды стали хранить их прямо в PostgreSQL — одна СУБД, один транзакционный контекст, привычные индексы. Однако вместе с гибкостью JSONB пришли новые SQL-операторы: ->, ->>, @>, ?, jsonb_path_query — и каждый из них может стать точкой инъекции, если использовать fmt.Sprintf вместо параметров.

Аналогия: представь библиотеку, где у каждой книги есть стандартная карточка (автор, год, ISBN) и приложенный конверт с произвольными заметками — рецензиями, пометками владельца, вырезками. JSONB — это такой конверт. Поиск «найди мне книги, где в заметках упоминается Достоевский» удобен, но если ты позволяешь читателю самому формулировать SQL-запрос для поиска по конверту, он может незаметно дописать «и заодно покажи мне каталог редких книг из хранилища». Параметризация — это «фильтр» библиотекаря: он принимает запрос только в строго определённом формате и сам составляет безопасную команду.

Посмотрим, как хакер может использовать поиск по JSON-ключам для извлечения всей базы данных.


1. Механика: JSON-операторы как вектор атаки

Перед нами типичный сценарий «гибкого API»: пользователь выбирает в UI, по какому полю JSON-документа выполнить поиск, и фронтенд передаёт это поле как query-параметр. На бэкенде разработчик решает, что раз поле — это идентификатор колонки в JSONB, а не значение, то его «нельзя» закрыть параметром, и собирает запрос через fmt.Sprintf. Это ошибка номер один в работе с JSONB.

Рассмотрим типичный динамический запрос в Go-приложении (например, на Fiber): Разработчик берет имя поля из запроса: key := c.Query("field") (например, name или email). И строит SQL: query := fmt.Sprintf("SELECT data->>'%s' FROM events", key)

Хакер вместо простого имени ключа присылает: ?field=name' UNION SELECT password FROM users--

Что в итоге попадает в базу:

// ❌ СМЕРТЕЛЬНО УЯЗВИМО: Внедрение через JSON-синтаксис
query := "SELECT data->>'name' UNION SELECT password FROM users--' FROM events"

База данных интерпретирует это как объединение двух совершенно разных запросов: первый ищет по JSON-полю, а второй — крадет пароли из таблицы users.

Обрати внимание: атакующий ничего не делал с самим JSON-документом. Он вставил инъекцию в имя ключа JSON, которое разработчик считал «безопасным идентификатором». На самом деле же это просто строка, попадающая в SQL-команду через конкатенацию — те же самые правила, что и для классического WHERE name='...'. Парсер PostgreSQL воспринимает результирующую строку как SQL-команду, и единственный его критерий валидности — это синтаксическая корректность. Если атакующий правильно балансирует кавычки и закрывает структуру комментарием --, запрос пройдёт компиляцию и выполнится.


2. Ловушка «безопасного» формата

Часто разработчики думают: «Я же не трогаю WHERE, я просто указываю ключ в JSON-объекте». Это опасное заблуждение. Для SQL-парсера нет принципиальной разницы, в какую часть строки внедряется вредоносный код. Любое место, где используется конкатенация и есть риск нарушения структуры кавычками — это потенциальная уязвимость.


3. Сравнение классической и JSON-инъекции

Тип атаки Вектор внедрения Пример нагрузки
Классика WHERE name = '%s' ' OR 1=1--
JSONB data->>'%s' name' UNION SELECT...--

4. Как распознать опасность в Go-коде

Использование операторов ->>, ->, @> внутри функций форматирования строк (fmt.Sprintf) или сырых запросов (db.Raw) — это верный признак уязвимости. Работа с JSONB-полями в Go обязательно должна сопровождаться использованием параметров ($1), так же как и в любых других частях SQL-запроса: data->>$1.

Корректный паттерн: ключ JSON-документа всегда передаётся как параметр, никогда — как часть SQL-строки. Запрос db.QueryRow("SELECT data->>$1 FROM events WHERE id=$2", fieldName, eventID) безопасен независимо от того, что находится в fieldName. PostgreSQL правильно декодирует параметр $1 как строку и применит JSON-оператор ->> к этому ключу, а попытка инъекции вроде name' UNION SELECT... просто превратится в поиск ключа с буквальным именем name' UNION SELECT..., который, естественно, ничего не найдёт.

Для linter'ов есть готовые правила: gosec ловит fmt.Sprintf с SQL-подобными строками (правило G201, G202); sqlc на этапе компиляции отвергает любые динамические колонки в запросах. Используйте эти инструменты в CI/CD пайплайне — они дешевле, чем post-mortem после инцидента.


JSONB — мощный инструмент, но он требует такой же строгой дисциплины, как и обычные таблицы. Без параметризации любой ваш «гибкий поиск» по JSON превращается в прямой путь к утечке данных.

Эволюция уязвимостей в JSONB: с появлением jsonb_path_query в PostgreSQL 12 появился новый класс инъекций — JSON Path Injection. Атакующий, контролирующий путь поиска, может выйти за пределы документа и обратиться к произвольным метаданным. CVE-2021-32672 в PostgreSQL описывает уязвимость в обработке @? оператора, которая позволяла читать произвольные файлы через специально сконструированный JSONPath. Урок тот же: динамические части SQL-запроса должны быть либо параметрами, либо строго whitelisted значениями из закрытого списка.

Соответствие стандартам: OWASP Top 10 A03:2021 Injection (включает все варианты, не только классический SQL); CWE-89; PCI-DSS 6.5.1.

В следующем подмодуле мы разберем одну из самых сложных тем — уязвимости в блоках сортировки (ORDER BY), которые невозможно закрыть стандартными плейсхолдерами.

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

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

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

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