2.2 Out-of-band (OOB) атаки через DNS и HTTP
Иллюзия параметризации: почему классическая защита проваливается на уровне архитектуры
В среде разработчиков бытует опасное заблуждение: стоит обернуть все запросы в 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, который вставляет их в запрос без проверки, возникает риск инъекции, которую крайне сложно обнаружить при поверхностном анализе одного компонента.
- Анализ динамических идентификаторов. Проверьте все места, где используются имена таблиц или колонок, приходящие извне. Единственный безопасный способ здесь — использование «белых списков» (allow-lists). Если значение не входит в список разрешенных строк, выполнение должно прерываться.
- Проверка конфигурации драйверов. В PHP/PDO убедитесь, что атрибут
PDO::ATTR_EMULATE_PREPARESустановлен вfalse. Эмуляция подготавливаемых запросов на стороне клиента может привести к специфическим обходам через многобайтовые кодировки, тогда как реальные Prepared Statements на стороне сервера гораздо надежнее. - Валидация типов данных. На уровне схемы БД и на уровне кода должны стоять жесткие ограничения. Если поле
user_idожидается как Integer, оно должно приводиться к этому типу до попадания в логику формирования запроса. Это простейший, но крайне эффективный фильтр против большинства автоматизированных сканеров.
Кейс: Обход фильтрации через архитектурную недоработку
Рассмотрим реальный сценарий. Приложение использует WAF и параметризацию для всех поисковых запросов. Однако в архитектуре предусмотрена функция «сохранения результатов поиска», которая записывает последний успешный запрос пользователя в таблицу логов для аналитики. Запись в логи происходит через триггер в базе данных или отдельный фоновый процесс. Атакующий формирует запрос, который легитимен для основного поиска (проходит WAF), но содержит скрытую нагрузку, которая срабатывает при выполнении фоновой задачи.
Это классический пример Second-Order SQLi, помноженный на архитектурную слепоту. Защита была сосредоточена на «входе» (API поиска), но полностью отсутствовала на этапе внутренней передачи данных между таблицами. Решением в данном случае является не усиление WAF, а внедрение строгой типизации данных в таблице логов и использование параметризованных запросов даже для внутренних системных операций, которые на первый взгляд кажутся «безопасными», так как работают с данными из собственной БД.
Практическое задание
Проведите ревизию небольшого фрагмента кода на Python (Flask/SQLAlchemy) или PHP.
- Найдите все места, где используются «сырые» запросы (
.execute(),raw(),db.query()). - Перепишите их с использованием параметризации, а если в запросе используются динамические имена колонок — реализуйте механизм белых списков.
- Спроектируйте схему прав доступа для пользователя БД этого приложения: какие привилегии являются избыточными (например,
FILE,SUPER,ALTER)? - Рассмотрите возможность внедрения триггеров или RLS для автоматической фильтрации данных по
user_id.
Чек-лист экспертного аудита
— Все входные параметры типизированы (int, uuid, string с ограничением длины). — Динамические имена таблиц/столбцов проходят через строгий Allow-list. — Отключена эмуляция подготавливаемых запросов в настройках драйвера БД. — Приложение подключается к БД под пользователем с минимально необходимыми правами (без доступа к системным таблицам). — Логирование запросов не создает условий для Second-Order атак (данные экранируются перед записью в лог). — Используются механизмы БД (Views, RLS) для изоляции данных между пользователями.
В следующем модуле мы перейдем к анализу инфраструктурных рисков и разберем, как неправильная настройка самого сервера баз данных может стать катализатором для полной компрометации сети, даже если ваше приложение идеально с точки зрения кода.