2.2 Out-of-band (OOB) атаки через DNS и HTTP

7 мин
Цель: Навык настройки инфраструктуры для эксфильтрации данных через сторонние каналы связи.

Иллюзия параметризации: почему классическая защита проваливается на уровне архитектуры

В среде разработчиков бытует опасное заблуждение: стоит обернуть все запросы в Prepared Statements, и проблема SQL-инъекций исчезнет навсегда. Однако для пентестера уровня Middle и выше параметризация — это лишь первый рубеж, который часто обходится через архитектурные лазейки, динамическую сортировку или специфические функции СУБД. Настоящая безопасность строится не на заплатках в коде, а на принципе эшелонированной защиты (Defense in Depth), где каждый слой системы — от прав пользователя в базе до структуры API — минимизирует поверхность атаки.

Представьте, что вы строите сейф. Параметризация — это надежный замок на двери. Но если стены сейфа сделаны из гипсокартона, грабителю не нужно возиться с замком. В роли таких «стен» выступают динамические имена таблиц, логика работы ORM-библиотек и права суперпользователя, под которыми приложение подключается к БД. Сегодня мы разберем, как выявлять эти структурные слабости при аудите и как проектировать системы, которые остаются устойчивыми, даже если злоумышленник нашел точку внедрения.

Анатомия уязвимого кода: PHP и Python в разрезе

В аудите кода важно не просто искать отсутствие экранирования, а понимать контекст выполнения запроса. В PHP, несмотря на повсеместное использование PDO, разработчики часто спотыкаются на динамических компонентах запроса. Параметризация в PDO работает только для значений (VALUES, WHERE), но она бессильна, если имя таблицы или столбца передается через переменную. Рассмотрим классический паттерн: выборка для сортировки в админ-панели. Разработчик доверяет параметру $_GET['sort'], полагая, что раз он не находится в блоке WHERE, то опасности нет. Однако именно здесь открывается путь к Blind-инъекциям через ORDER BY, где злоумышленник может использовать конструкции CASE для посимвольного перебора данных.

Python-разработчики, полагаясь на мощные ORM вроде SQLAlchemy или Django ORM, часто создают уязвимости, когда пытаются «оптимизировать» запросы через метод .extra() или сырой SQL (raw()). Типичная ошибка в Python — использование f-строк внутри функций выполнения запросов. Даже если библиотека поддерживает параметры, использование f-строки формирует окончательный запрос до того, как драйвер БД успеет его обработать. Это превращает высокоуровневый безопасный код в решето, где атакующий может встроить подзапрос прямо в середину бизнес-логики, обходя все встроенные фильтры ORM.

Архитектурный Defense in Depth на уровне СУБД

Профессиональный подход к защите требует признания факта: код может быть скомпрометирован. Поэтому вторым эшелоном выступает конфигурация самой базы данных. Принцип наименьших привилегий (POLP) здесь должен выполняться фанатично. Если веб-приложение только читает статьи из блога, у его пользователя в БД не должно быть прав на DROP TABLE, GRANT или выполнение системных команд вроде xp_cmdshell. Более того, экспертный аудит подразумевает разделение пользователей БД по ролям: один для чтения, другой для записи, третий — для аналитических отчетов с доступом только к представлениям (views) вместо таблиц.

Использование представлений (Views) и хранимых процедур — это не только вопрос производительности, но и мощный инструмент безопасности. Представления позволяют скрыть реальную структуру таблиц и ограничить доступ к чувствительным колонкам, таким как хэши паролей или токены сессий. Даже если злоумышленник получит возможность выполнить SELECT *, он увидит лишь те данные, которые вы явно разрешили экспонировать. В сочетании с Row-Level Security (RLS) в PostgreSQL это позволяет ограничить видимость строк на уровне ядра БД, делая невозможным извлечение чужих данных даже при наличии успешной SQL-инъекции.

Практика аудита: поиск скрытых зависимостей

При проведении аудита кода ваша задача — отследить путь данных (taint analysis) от источника (HTTP-запрос) до стока (выполнение запроса в БД). Особое внимание уделяйте местам, где данные проходят через несколько слоев обработки. В современных микросервисах часто встречается ситуация, когда один сервис доверяет другому. Если Service A передает «очищенные» данные в Service B, который вставляет их в запрос без проверки, возникает риск инъекции, которую крайне сложно обнаружить при поверхностном анализе одного компонента.

  1. Анализ динамических идентификаторов. Проверьте все места, где используются имена таблиц или колонок, приходящие извне. Единственный безопасный способ здесь — использование «белых списков» (allow-lists). Если значение не входит в список разрешенных строк, выполнение должно прерываться.
  2. Проверка конфигурации драйверов. В PHP/PDO убедитесь, что атрибут PDO::ATTR_EMULATE_PREPARES установлен в false. Эмуляция подготавливаемых запросов на стороне клиента может привести к специфическим обходам через многобайтовые кодировки, тогда как реальные Prepared Statements на стороне сервера гораздо надежнее.
  3. Валидация типов данных. На уровне схемы БД и на уровне кода должны стоять жесткие ограничения. Если поле user_id ожидается как Integer, оно должно приводиться к этому типу до попадания в логику формирования запроса. Это простейший, но крайне эффективный фильтр против большинства автоматизированных сканеров.

Кейс: Обход фильтрации через архитектурную недоработку

Рассмотрим реальный сценарий. Приложение использует WAF и параметризацию для всех поисковых запросов. Однако в архитектуре предусмотрена функция «сохранения результатов поиска», которая записывает последний успешный запрос пользователя в таблицу логов для аналитики. Запись в логи происходит через триггер в базе данных или отдельный фоновый процесс. Атакующий формирует запрос, который легитимен для основного поиска (проходит WAF), но содержит скрытую нагрузку, которая срабатывает при выполнении фоновой задачи.

Это классический пример Second-Order SQLi, помноженный на архитектурную слепоту. Защита была сосредоточена на «входе» (API поиска), но полностью отсутствовала на этапе внутренней передачи данных между таблицами. Решением в данном случае является не усиление WAF, а внедрение строгой типизации данных в таблице логов и использование параметризованных запросов даже для внутренних системных операций, которые на первый взгляд кажутся «безопасными», так как работают с данными из собственной БД.

Практическое задание

Проведите ревизию небольшого фрагмента кода на Python (Flask/SQLAlchemy) или PHP.

  1. Найдите все места, где используются «сырые» запросы (.execute(), raw(), db.query()).
  2. Перепишите их с использованием параметризации, а если в запросе используются динамические имена колонок — реализуйте механизм белых списков.
  3. Спроектируйте схему прав доступа для пользователя БД этого приложения: какие привилегии являются избыточными (например, FILE, SUPER, ALTER)?
  4. Рассмотрите возможность внедрения триггеров или RLS для автоматической фильтрации данных по user_id.

Чек-лист экспертного аудита

— Все входные параметры типизированы (int, uuid, string с ограничением длины). — Динамические имена таблиц/столбцов проходят через строгий Allow-list. — Отключена эмуляция подготавливаемых запросов в настройках драйвера БД. — Приложение подключается к БД под пользователем с минимально необходимыми правами (без доступа к системным таблицам). — Логирование запросов не создает условий для Second-Order атак (данные экранируются перед записью в лог). — Используются механизмы БД (Views, RLS) для изоляции данных между пользователями.

В следующем модуле мы перейдем к анализу инфраструктурных рисков и разберем, как неправильная настройка самого сервера баз данных может стать катализатором для полной компрометации сети, даже если ваше приложение идеально с точки зрения кода.