РАЗБОР Статья

Аутентификация: приложение vs СУБД - разбор атак

Сергей Попов
Сергей Попов Red Team · 6,7 тыс. сообщений
Подписаться
82
[ обложка статьи ]
Режим чтения
На тёмном антистатическом коврике лежат два жетона: стальной цилиндр с гравировкой «SERVICEACCOUNT GRANT ALL» и чёрный чип с надписью «APP AUTH ≠ DB AUTH» на фоне терминала с SQL-запросом.


Понедельник, 9:15 утра. На SOC-дашборде - алерт: нетипичный объём SELECT-запросов к таблице payments из одного микросервиса. Разбираем - и картина знакомая до боли: атакующий через SSRF вытащил connection string из переменной окружения, подключился к PostgreSQL напрямую и за 12 минут выгрузил 40 тысяч записей. Приложение аккуратно проверяло JWT на каждый API-вызов, но база видела только один service account с правами на все таблицы. Один. На всё.

Этот разрыв между аутентификацией на уровне приложения и встроенной аутентификацией СУБД стоил компании утечки персональных данных с перспективой оборотного штрафа. И он повторяется в каждом втором аудите, где архитектура строилась по принципу "приложение знает, кто пользователь, а база - нет".

Две модели аутентификации: архитектурные различия​

Аутентификация на уровне приложения​

Здесь приложение берёт на себя всю логику аутентификации и авторизации. Пользователь проходит проверку через OAuth 2.0, JWT-токен или сессионный cookie. Приложение валидирует identity, определяет доступ - через RBAC-middleware, policy engine или ORM-фильтры. К СУБД приложение подключается через единый service account (или пул) с постоянным набором привилегий. Подробнее - в нашем статье о атаки на аутентификацию.

Поток: пользователь проходит JWT-валидацию → middleware проверяет роли -> запрос уходит через connection pool в СУБД от имени service account с широкими привилегиями.

Что из этого следует:
  • Вся логика авторизации живёт в коде приложения. СУБД не различает конечных пользователей - видит только service account.
  • Масштабирование через connection pooling (PgBouncer, HikariCP) работает просто: все соединения идут от одного пользователя.
  • Аудит на уровне СУБД показывает только действия service account. Реальный пользователь - только в логах приложения. И если логи приложения не пишутся или ротируются раз в сутки - удачи в расследовании.
  • По OWASP API Security Top 10, API-трафик часто несёт чувствительные токены через потенциально недоверенные сети, и именно application-level auth отвечает за проверку каждого запроса.
Критический риск очевиден: если атакующий обходит приложение - через SSRF, прямой доступ к порту СУБД, компрометацию connection string - database-слой ничего не фильтрует. Service account открывает доступ ко всему, на что выданы GRANT. Всё. Точка.

Встроенная аутентификация СУБД​

Здесь каждый пользователь (или группа) имеет собственную учётную запись в СУБД. Аутентификация проходит через встроенные механизмы: парольная проверка (scram-sha-256 в PostgreSQL), Kerberos/GSSAPI, LDAP, клиентские сертификаты или PAM. Авторизация - через GRANT/REVOKE, роли и Row-Level Security (RLS).

Поток: приложение передаёт credentials пользователя -> СУБД аутентифицирует через pg_hba.conf -> применяет GRANT/REVOKE и RLS к каждому запросу.

Что даёт эта модель:
  • СУБД знает, кто конкретно выполняет запрос. Аудит через pgAudit (PostgreSQL), SQL Server Audit или MySQL Enterprise Audit привязан к реальному пользователю - не к безликому app_service.
  • Привилегии гранулярны - до уровня столбца или строки через RLS.
  • Connection pooling усложняется: каждому пользователю нужно своё соединение или механизм SET ROLE при смене контекста. Тут начинается боль.
  • Интеграция с доменной инфраструктурой (Active Directory, FreeIPA) через Kerberos позволяет строить единый SSO-контур. Этот подход описан и в документации СУБД Jatoba, и рекомендован PostgreSQL STIG.
На практике чистая модель "каждый пользователь = роль в СУБД" встречается в корпоративных BI-системах и DWH, где число пользователей - десятки или сотни. Для SaaS с миллионом пользователей это архитектурно неприменимо - и попытки натянуть эту модель заканчиваются предсказуемо.

Сравнение методов аутентификации: модели контроля доступа СУБД​

1792900135464.webp

КритерийApplication-levelDatabase-nativeКогда НЕ подходит
Гранулярность авторизацииRBAC/ABAC в middlewareGRANT/REVOKE + RLS до строки/столбцаApp-level - когда нужен аудит per-user на уровне БД
Аудит: кто выполнил запросService account (пользователь - только в логах приложения)Реальный пользователь через pgAuditDB-native - при тысячах пользователей аудит генерирует терабайты логов
МасштабируемостьВысокая, connection pooling без ограниченийОграничена числом соединенийDB-native - в микросервисах с горизонтальным масштабированием
Устойчивость к обходу приложенияНизкая: обход = полный доступВысокая: привилегии роли действуют всегдаApp-level - при SSRF/прямом доступе к порту СУБД
Совместимость с микросервисамиНативнаяТребует Kerberos delegation или credential forwardingDB-native - в Kubernetes с ephemeral pods
Insider threat visibilityЗависит от логирования приложенияНативный аудит на уровне СУБДApp-level - при расследовании инцидента без корреляции логов
Сложность внедренияСредняя (зависит от IAM-стека)Высокая (синхронизация ролей, pg_hba.conf)DB-native - в командах без DBA-экспертизы

Это не бинарный выбор. В зрелых архитектурах комбинируют оба подхода: приложение аутентифицирует пользователя и выполняет SET ROLE в рамках сессии СУБД, сохраняя гранулярность database-level привилегий при connection pooling. Но до такой зрелости доживают не все.

Уязвимости аутентификации базы данных: поверхность атаки и TTPs​

1792900190208.webp

Обход SQL injection аутентификации​

При application-level auth SQL-инъекция в запросе авторизации может полностью обойти проверку прав. Классика - запрос SELECT * FROM users WHERE login='...' AND pass='...', куда атакующий подставляет ' OR 1=1 --. Если приложение строит запросы конкатенацией вместо параметризованных выражений, аутентификация обходится за один HTTP-запрос. Один.

По классификации OWASP это попадает одновременно под A07:2021 - Identification and Authentication Failures и A01:2021 - Broken Access Control (94% приложений тестировались на ту или иную форму broken access control).

При database-native auth SQL-инъекция не обходит аутентификацию СУБД - атакующий уже аутентифицирован через pg_hba.conf или Kerberos-тикет. Но инъекция может привести к повышению привилегий: выполнение ALTER ROLE или вызов привилегированной функции от имени текущей роли при неаккуратных GRANT. Тут уже другая головная боль.

Privilege escalation через базу данных​

Бизнес-логика атаки: злоумышленник получает начальный доступ к СУБД и стремится расширить привилегии для выгрузки чувствительных таблиц или выполнения команд ОС. Зачем? Продажа выгруженных данных, шифрование для выкупа, либо использование скомпрометированного сервера как плацдарма для lateral movement. Мотивация простая - деньги или доступ к следующей цели.

Типичные сценарии по каждой модели:

Application-level модель:

Утечка connection string из конфигов, переменных окружения или логов - техника Credentials In Files (T1552.001, Credential Access). Даёт полные привилегии service account. На практике connection string валяется в .env, docker-compose.yml, а иногда и в git-истории. Service account с SUPERUSER или избыточными GRANT - мисконфигурация, соответствующая OWASP A05:2021 - Security Misconfiguration. SSRF до порта СУБД - Exploit Public-Facing Application (T1190, Initial Access). Если сетевой доступ к порту 5432/3306/1433 не ограничен, атакующий подключается напрямую.

Database-native модель:

Password Spraying (T1110.003, Credential Access) по открытому порту СУБД при слабой парольной политике - банально, но до сих пор работает. Бэкдор в PAM-модуле - Pluggable Authentication Modules (T1556.003, Persistence + Credential Access). Атакующий модифицирует цепочку PAM для перехвата паролей и поддержания доступа. Использование Valid Accounts (T1078, Initial Access + Privilege Escalation) - скомпрометированный сотрудник или оставленная учётная запись уволенного (привет, offboarding-процессы). Модификация процесса аутентификации (T1556, Defense Evasion + Persistence) - подмена конфигурации pg_hba.conf для добавления trust-доступа с произвольного IP.

MITRE ATT&CKТактикаApplication-levelDatabase-native
T1190 - Exploit Public-Facing ApplicationInitial AccessSSRF до connection stringПрямая эксплуатация открытого порта СУБД
T1110.003 - Password SprayingCredential AccessПеребор через API приложенияПеребор учётных записей СУБД
T1078 - Valid AccountsPersistence, Priv EscКомпрометация service accountКомпрометация DBA-аккаунта
T1552.001 - Credentials In FilesCredential AccessConnection string в .env, docker-composeПароли в .pgpass, my.cnf
T1556.003 - Pluggable Auth ModulesPersistenceНеприменимоБэкдор в PAM-цепочке
T1556 - Modify Authentication ProcessCredential AccessПодмена JWT-секретаМодификация pg_hba.conf

Detection: правила корреляции для SOC​

Аномалии в application-level модели​

Основная сложность: все запросы идут от service account, поэтому корреляцию нужно строить на стыке логов приложения и СУБД. Два источника, которые нужно склеить - и у каждого свой формат, свой timestamp, своя ротация.

Что мониторить и на что ставить алерты:
  • Всплеск запросов к чувствительным таблицам. Baseline: средний объём SELECT за 15-минутный интервал. Алерт при отклонении >3 сигма. Если за 5 минут от одного source IP приходит >100 SELECT к таблице с PII при baseline <10 - это аномалия.
  • Запросы к таблицам, которые конкретный микросервис не трогает. Требует корреляции application logs (service name) с DB audit (table name). Каждый микросервис должен иметь задокументированный перечень используемых таблиц. Да, это значит - документация. Да, её никто не любит писать.
  • Прямые подключения мимо connection pool. Алерт: source IP подключения к СУБД не совпадает ни с одним IP из пула серверов приложения. Если кто-то подключился с IP, которого нет в whitelist - это либо инцидент, либо DBA забыл предупредить.
  • Изменение connection string в runtime. Мониторинг через EDR на хостах приложения - отслеживание изменений в переменных окружения и конфигурационных файлах.

Аномалии во встроенной аутентификации СУБД​

Аудит здесь гранулярнее - каждый запрос привязан к реальному пользователю. Но нужно настроить pgAudit:
SQL:
-- pgAudit: включение логирования DDL и role-операций
ALTER SYSTEM SET pgaudit.log = 'ddl, role';
ALTER SYSTEM SET pgaudit.log_parameter = on;
SELECT pg_reload_conf();
Логи попадают в syslog и далее в SIEM для корреляции. Минимальный набор алертов:
  • Auth failures. Порог: >5 неуспешных попыток аутентификации от одного источника за 10 минут - detection для Brute Force (T1110). Казалось бы, очевидно. Но я регулярно вижу конфигурации, где этот алерт просто не настроен.
  • Логин DBA-роли в нерабочее время или с нетипичного IP-адреса. Требует базового baseline по времени и гео.
  • DDL-команды от неожиданных ролей. Выполнение ALTER ROLE, GRANT, CREATE EXTENSION не из утверждённого IP-диапазона.
  • Изменения pg_hba.conf и перезагрузка конфигурации через pg_reload_conf() - мониторинг через file integrity monitoring.

Insider threat: что видно в каждой модели​

В application-level модели инсайдер-разработчик с доступом к connection string получает полные привилегии service account. Отследить его действия можно только если приложение записывает user ID в каждый SQL-запрос - через SET app.current_user или comment-based audit trail. На практике это реализуют единицы. Остальные узнают о проблеме на постинцидентном разборе.

В database-native модели действия инсайдера привязаны к его персональной роли. pgAudit фиксирует каждый запрос с username. При incident response не нужно коррелировать логи трёх систем - аудит СУБД сразу показывает, кто выгрузил таблицу, когда и откуда. Для постинцидентного разбора это преимущество, которое сложно переоценить.

Hardening: безопасность доступа к базам данных​

1792900222142.webp

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Отдельная тема - connection pooling и безопасность. PgBouncer в режиме transaction не поддерживает SET ROLE без дополнительных workaround. Варианты: переход в режим session (с потерей производительности) или использование auth_query + server_reset_query для передачи контекста пользователя. Этот trade-off нужно закладывать на этапе проектирования архитектуры аутентификации СУБД, а не когда прилетел инцидент и всё горит.

Опыт последних двух лет показывает устойчивую закономерность: команды, которые строят защиту только на одном слое - только application-level или только database-native - проигрывают при первом серьёзном инциденте. Application-level auth отлично масштабируется и интегрируется с OAuth/OIDC, но создаёт single point of failure в виде service account. Встроенная аутентификация СУБД даёт гранулярный аудит и реальный defense in depth, но усложняет DevOps-процессы и требует DBA-экспертизы, которой у большинства команд просто нет.

На постинцидентных разборах я вижу одну и ту же ошибку: команда выбирает "или-или" и строит модель угроз вокруг одного слоя. Атакующий находит путь через другой. SSRF до базы обходит JWT. Компрометация PAM-модуля обходит GRANT/REVOKE. Единственная рабочая стратегия - layered auth, где приложение проверяет identity и передаёт контекст в СУБД через SET ROLE, а СУБД дополнительно ограничивает доступ через RBAC и RLS. Это заметно сложнее в реализации - но именно такая связка выдерживает реальные атаки, а не только compliance-аудиты. На codeby.net есть тред, где разбираем конфиги pg_hba.conf и pgAudit под разные архитектуры - если настраиваешь подобную связку, стоит заглянуть.
Полезно

Комментарии

0