Frozen Privilege: удаленное выполнение кода в Snowflake-Managed PostgreSQL

Фото: 358611 (Pixabay) — https://pixabay.com/photos/iceberg-antarctica-polar-ice-sea-404966/

Источник: Varonis•

Frozen Privilege: удаленное выполнение кода в Snowflake-Managed PostgreSQL

Команда Varonis Threat Labs обнаружила уязвимость удаленного выполнения кода (RCE) в Snowflake-managed PostgreSQL, которая превращает обычную привилегию базы данных в контроль над базовым сервером путем объединения легитимных функций в цепочку. 

Varonis Threat Labs обнаружила уязвимость удаленного выполнения кода (RCE) в Snowflake-managed PostgreSQL, которая позволяет превратить обычные привилегии базы данных в контроль над базовым сервером путем объединения легитимных функций.

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

Цепочка начинается с роли, не являющейся суперпользователем, и объединяется с легитимным поведением PostgreSQL для получения полного выполнения команд на уровне ОС на хосте базы данных, обходя опубликованные меры защиты Snowflake, не отключая их. Успешная атака может позволить злоумышленнику подделать или уничтожить файлы базы данных, саботировать восстановление и скрыть активность вне обычных журналов аудита SQL.

Мы раскрыли эту цепочку Snowflake, и они быстро устранили проблему на стороне сервера. Клиентам Snowflake не нужно предпринимать дальнейших действий. Varonis Threat Labs впервые представила наше открытие эксклюзивно на BSides NYC в Cloud Village в октябре 2026 года.

Varonis Threat Labs обнаружила несколько уязвимостей в PostgreSQL, включая удаленное выполнение кода в AWS RDS через PL/Perl и PL/Rust, а также RCE в Azure Cosmos для PostgreSQL. Вместе они доказывают, что цепочки повышения привилегий в управляемом PostgreSQL являются системным паттерном. Продолжайте читать, чтобы узнать, как мы объединили функции Postgres в Snowflake, и ознакомиться с пошаговым разбором этой цепочки.

Что такое Snowflake Postgres?

Snowflake Postgres позволяет клиентам создавать, управлять и использовать экземпляры PostgreSQL непосредственно с платформы. По заявлению Snowflake, каждый экземпляр «запускает сервер базы данных Postgres на выделенной виртуальной машине, управляемой Snowflake», и вы подключаетесь к нему напрямую, используя любой стандартный клиент Postgres.

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

Цепочка

В общих чертах цепочка выглядела следующим образом:

  • Отправной точкой была snowflake_admin, роль, не являющаяся суперпользователем, единственным примечательным атрибутом которой был CREATEROLE.
  • pg_write_all_data позволяет изменять таблицу заданий pg_cron, задания которой выполняются через локальный сокет от имени любого пользователя без пароля. Мы использовали это для выполнения SQL от имени postgres и повышения привилегий до суперпользователя.
  • Snowflake блокирует обычные примитивы SQL-to-OS; мы использовали внутренний язык PostgreSQL и большие объекты (large objects) для восстановления чтения/записи файлов.
  • Затем мы объединили эту запись файлов с archive_command — определяемой конфигурацией командой оболочки, которая запускается при каждом архивировании Write-Ahead Logging (WAL) — для достижения произвольного выполнения команд (RCE) от имени postgres.
  • Конечный результат: полное RCE на уровне ОС в управляемой базе данных — обход всех опубликованных мер защиты без отключения какой-либо из них.

Пять звеньев позволяют ограниченной роли, не являющейся суперпользователем, дойти до выполнения команд ОС. Каждое из них является задокументированным, предусмотренным поведением PostgreSQL. При объединении в цепочку они становятся катастрофическими и позволяют выполнять код внутри экземпляра.

Как работает цепочка звено за звеном

  • CREATEROLE как точка опоры. Путешествие начинается с snowflake_admin, чей единственный примечательный атрибут — CREATEROLE. Начиная с PostgreSQL 14, одного этого атрибута достаточно для самостоятельного предоставления предопределенных системных ролей, включая pg_read_all_data и pg_write_all_data. Ни одна из этих ролей не является суперпользователем, который явно исключен из этого набора.
  • Самостоятельное предоставление ролей доступа к данным. Используя CREATEROLE, роль предоставляет себе pg_read_all_data и pg_write_all_data, мгновенно получая доступ на чтение и запись ко всем таблицам и системным каталогам в кластере.
  • Взлом pg_cron для получения прав суперпользователя. Имея на руках pg_write_all_data, мы можем записывать данные в таблицу заданий pg_cron — обычную таблицу, которой планировщик доверяет безоговорочно. Поскольку pg_cron выполняет задания через локальный сокет от имени любого пользователя без пароля, изменение имени пользователя задания на postgres позволяет нам выполнять SQL от имени суперпользователя и внедрить бэкдор SECURITY DEFINER — функцию, которая выполняется с привилегиями пользователя, создавшего ее, а не пользователя, который ее вызывает (как setuid-программа в Unix). Как только она становится собственностью postgres, любой пользователь с низкими привилегиями, вызывающий ее, выполняет код с правами суперпользователя.
  • Восстановление заблокированных примитивов через internal. Будучи суперпользователем, мы можем создавать функции, используя LANGUAGE internal, который привязывает новые имена SQL к скомпилированным C-символам, все еще присутствующим в бинарном файле, включая те самые функции, которые Snowflake заблокировала по имени.
  • Произвольное чтение и запись файлов. Используя эти восстановленные функции, lo_export записывает данные везде, где может пользователь ОС postgres, а восстановленный pg_read_file извлекает любой файл с диска.
  • От записи файла к выполнению команд. С помощью произвольной записи файла мы перезаписали postgresql.auto.conf через lo_export, чтобы внедрить команду оболочки в archive_command, которую PostgreSQL выполняет через /bin/sh -c каждый раз при архивировании завершенного сегмента WAL (WAL — это журнал транзакций PostgreSQL, записывающий каждое изменение до того, как оно попадет на диск). Пользовательское C-расширение послужило альтернативным путем.

CREATEROLE — это примитив повышения привилегий

CREATEROLE широко рассматривается как незначительная привилегия, но на практике это одна из самых недооцененных привилегий PostgreSQL.

PostgreSQL 16 ужесточил CREATEROLE, но одна лазейка осталась. Она по-прежнему может самостоятельно предоставлять предопределенные системные роли pg_*, у которых нет «создателя» и для предоставления которых никогда не требуется суперпользователь. У snowflake_admin rolcreaterole = true.

Привилегия CREATEROLE — это все, с чего мы начинаем, и все, что нам нужно.

В нашем исследовании мы успешно предоставили широкий доступ к данным по всему экземпляру.

С помощью pg_read_all_data мы могли читать каждую таблицу и каталог, включая pg_authid, который раскрывает верификатор учетных данных каждой роли. Две роли суперпользователя (postgres и snowflake_superuser) имели rolsuper=true, но нам нужен был способ выполнять SQL от имени одной из них.

Здесь на помощь пришел pg_cron. Расширение загружается через shared_preload_libraries и, имея pg_write_all_data, мы могли записывать данные напрямую в таблицу cron.job, нарушая предположение о том, что только доверенные компоненты могут ее изменять. Мы изменили имя пользователя задания на «postgres» и установили его команду на создание функции SECURITY DEFINER, которая выполняла бы любой SQL от имени суперпользователя и предоставляла бы роль обратно нам.

Когда планировщик срабатывал, он запускался от имени postgres, создавал нашу функцию и передавал нам ключи. Мы повысили нашу собственную роль до snowflake_superuser и получили права суперпользователя, ни разу не вызвав ALTER ROLE ... SUPERUSER.

Таким образом, мы получили полный доступ суперпользователя PostgreSQL, ни разу не вызвав ALTER ROLE ... SUPERUSER, что блокирует Snowflake.

Превращение суперпользователя в чтение и запись файлов

В самостоятельно размещенном PostgreSQL суперпользователь фактически означает выполнение кода. Задача управляемого провайдера — разорвать эту эквивалентность. Получение прав суперпользователя означало лишь то, что мы достигли границы, которую защищает Snowflake.

Права суперпользователя дали нам основу для восстановления примитивов чтения/записи файлов — но не самого выполнения команд. Чтение файлов позволяет проводить разведку; запись файлов — подготавливать полезную нагрузку; следующий этап превращает её в запущенный процесс.

Мы обратились к классическим примитивам SQL-to-OS. Snowflake заблокировала каждый из них:

  • COPY ... TO PROGRAM — заблокировано
  • ALTER SYSTEM — заблокировано
  • pg_read_file() / pg_read_server_files — заблокировано

Каждый известный путь был тупиком — именно там, где останавливается большинство проверок безопасности.

Поиск двери: внутренний язык

Вместо того чтобы атаковать функции, мы задались вопросом, как они устроены. Переопределение внутреннего C-символа через CREATE FUNCTION ... LANGUAGE internal — это документированное поведение. Это исследование показывает, что блокировщик на уровне каталога провайдера можно полностью обойти — поскольку скомпилированные символы остаются в бинарном файле — и связать это с RCE на уровне ОС.

Документация PostgreSQL удивительно откровенна:

Мы начали с подтверждения того, что внутренний язык вообще существует в экземпляре. \dL перечисляет процедурные языки:

postgres=# \dLinternal присутствует. Это прямая привязка к C-функциям, скомпилированным в сервер, включая те самые, которые Snowflake «заблокировала». Создание функций LANGUAGE internal доступно только суперпользователю.

Это сработало. my_read_file делает всё то же самое, что и pg_read_file, без каких-либо проверок привилегий. Блокировка Snowflake была замком на дверной ручке; мы просто прикрутили вторую ручку. Каждый «заблокированный» примитив был доступен через одну команду CREATE FUNCTION.

Примечание: всё еще ограничено нашей собственной однопользовательской виртуальной машиной

До попытки выполнения кода этого одного примитива было достаточно, чтобы взломать хост:

  • /etc/passwd — подтверждение того, что postgres работает под uid=26
  • Внутренний закрытый ключ mTLS и сертификат арендатора Snowflake
  • Учетные данные AWS STS арендатора в открытом виде

От записи файла к RCE

Как только вы понимаете, что «заблокировано» означает «имя SQL было удалено», остальное становится делом техники.

Листинг каталогов. Мы перепривязали pg_ls_dir() через internal (prosrc = ‘pg_ls_dir’) вместе с pg_stat_file, чтобы обходить каталоги и находить файлы.

Запись файлов. lo_export(oid, path) записывает данные везде, где может пользователь postgres, без какой-либо «песочницы».

Имея возможность произвольной записи файлов, мы нацелились на postgresql.auto.conf — файл конфигурации, который PostgreSQL считывает при перезагрузке. Мы подготовили полезную нагрузку как большой объект (large object) и экспортировали её прямо поверх файла конфигурации, используя наш восстановленный обертку lo_export.

Полезная нагрузка перезаписала archive_command — GUC, значение которого PostgreSQL выполняет через /bin/sh -c при каждом завершенном сегменте WAL (Write-Ahead Log) от имени пользователя ОС postgres. Это самый чистый командный sink на стороне сервера в PostgreSQL, обычно защищенный через ALTER SYSTEM, который мы теперь контролируем через другой путь.

С одной ролью, не являющейся суперпользователем, мы получили полный RCE на уровне ОС — uid=26, пользователь postgres, на экземпляре, управляемом Snowflake.

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

Все меры защиты и способы их обхода

Заблокированная операция

Использованный обход

ALTER SYSTEM

Прямая запись файла в postgresql.auto.conf через lo_export

COPY TO PROGRAM

Инъекция в archive_command и пользовательское расширение .so

pg_read_file()

Обертка LANGUAGE internal, привязанная к тому же символу

ALTER ROLE ... SUPERUSER

GRANT snowflake_superuser через бэкдор SECURITY DEFINER

COPY FROM/TO FILE

Интерфейс больших объектов lo_import / lo_export

Что злоумышленники могут сделать дальше

Утекшие учетные данные администратора обычно являются проблемой типа «сменил пароль и забыл». Эта цепочка превращает их в постоянный плацдарм для угроз.

Злоумышленник создает хранимую функцию, которая выполняет команды ОС. Поскольку это объект базы данных, он сохраняется независимо от учетных данных, которые его создали. Смена учетных данных его не затрагивает. Поскольку функция выполняется от имени учетной записи ОС postgres — владельца всего каталога данных — злоумышленник может полностью обойти базу данных:

  • Невидимое манипулирование данными. Редактирование файлов кучи (heap files) на диске изменяет содержимое таблиц, не проходя через SQL. Ограничения, триггеры, безопасность на уровне строк и аудит внутри базы данных — всё это обходится. Нет SQL-запроса для записи, и изменение не имеет автора с точки зрения базы данных.
  • Саботаж восстановления. Изменение или удаление сегментов WAL нарушает восстановление на момент времени (PITR) и репликацию на резервные серверы. Повреждение pg_control или clog может сделать зафиксированные транзакции нечитаемыми или полностью предотвратить запуск кластера.
  • Разрушение, эквивалентное вирусам-вымогателям. Файлы данных могут быть усечены, удалены или зашифрованы на месте. В случае шифрования база данных не может прочитать свои собственные данные, и резервные копии могут не решить проблему, если они были созданы после компрометации.
  • Манипуляция локальными логами. Логи PostgreSQL могут быть изменены или удалены, чтобы скрыть активность. (Логи, отправленные за пределы хоста в централизованную систему, не пострадают. Это скрывает доказательства только внутри экземпляра.)

Рекомендации по защите

Snowflake устранила эту цепочку на стороне сервера; управляемым клиентам никаких действий предпринимать не нужно. Значение ниже приведено для всех, кто управляет PostgreSQL, поскольку каждое звено в цепочке — это стандартный PostgreSQL, работающий так, как задумано.

Первопричина — избыточно предоставленный атрибут CREATEROLE в сочетании с функциями, которые доверяют идентификации, предоставленной вызывающим абонентом.

Для управляемых клиентов PostgreSQL:

  • Используйте аутентификацию на основе токенов Snowflake. Замените статические пароли PostgreSQL на краткосрочные токены, выпущенные Snowflake. Это привязывает аутентификацию к уровню идентификации Snowflake (MFA, SSO, централизованный аудит) и ограничивает окно возможностей злоумышленника в случае компрометации учетных данных.
  • Включите сетевые политики. Ограничьте IP-адреса и диапазоны, которые могут подключаться к вашему экземпляру Snowflake Postgres. Используйте AWS PrivateLink или Azure Private Link вместо того, чтобы открывать экземпляр в публичный интернет.
  • Применяйте роли с минимальными привилегиями. Не используйте snowflake_admin для рабочих нагрузок приложений. Создавайте выделенные роли, ограниченные конкретными схемами и таблицами. Никогда не предоставляйте CREATEROLE ролям приложений или сервисных учетных записей — это была точка входа для всей цепочки.
  • Внедрите гранулярный контроль доступа на основе ролей (RBAC). Строго ограничьте привилегии пользователей базы данных. Определите гранулярные элементы управления доступом на основе пользователей и ролей, строго исходя из операционных потребностей, сводя к минимуму область и продолжительность привилегированного доступа.
  • Следите за повышением привилегий. Настройте оповещения на операторы GRANT, которые назначают CREATEROLE, pg_read_all_data, pg_write_all_data или членство в системных ролях. Периодически проверяйте задания pg_cron и функции SECURITY DEFINER на предмет чего-либо неожиданного.

Для команд, запускающих PostgreSQL самостоятельно:

  • Относитесь к CREATEROLE как к правам, близким к суперпользователю. Отозвите их у ролей приложений. Ограничьте pg_cron так, чтобы cron.job.username мог быть только пользователем сессии, но никогда не ролью с высокими привилегиями, такой как postgres.
  • Ограничьте создание внутренних и недоверенных функций. Запретите ролям, не являющимся платформенными, создавать функции LANGUAGE internal или LANGUAGE c. Скрытие имени функции — это не то же самое, что удаление возможности.
  • Защитите конфигурационные файлы. Сделайте postgresql.auto.conf неизменяемым или заблокируйте запись lo_export в каталог данных; archive_command выполняется через /bin/sh -c при каждом архивировании WAL.
  • Изолируйте движок. Запускайте postgres в реальном домене SELinux, отключите неиспользуемые расширения и не открывайте порты PostgreSQL для публичного доступа.

Мы следовали процессу скоординированного раскрытия информации совместно с Snowflake:

  • 31 мая 2026 г. — Отчет отправлен. Полная цепочка повышения привилегий и последующее RCE были переданы через процесс раскрытия информации о безопасности Snowflake.

31 мая 2026 г. — Отчет отправлен. Полная цепочка повышения привилегий и последующее RCE были переданы через процесс раскрытия информации о безопасности Snowflake.

  • 1 июня 2026 г. — Отчет получен, начата внутренняя сортировка.

1 июня 2026 г. — Отчет получен, начата внутренняя сортировка.

  • 9 июня 2026 г. — Snowflake подтвердила находку, заявив: «Мы проанализировали поведение внутри компании и согласны с тем, что локальное выполнение кода внутри виртуальной машины Snowflake Postgres нарушает предполагаемую границу безопасности». Соответствующие проблемы были устранены до публикации. Мы благодарим команду безопасности Snowflake за быстрый ответ и сотрудничество в процессе раскрытия информации.
  • 17 октября 2026 г. — Мы впервые представили наши результаты на конференции BSides NYC в Cloud Village.

Итог

Одна роль без прав суперпользователя с атрибутом CREATEROLE превратилась в полноценное RCE на уровне ОС в управляемом экземпляре PostgreSQL, обойдя все опубликованные меры защиты. Ни один из компонентов сам по себе не является новым; эскалация использовала PostgreSQL именно так, как он был спроектирован.

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

Snowflake уже защитилась от очевидных путей перехода от SQL к ОС еще до нашего появления — это реальная глубокоэшелонированная защита, а не просто формальное выполнение требований. Мы сообщили о полной цепочке в мае 2026 года, и они быстро отреагировали и устранили ее на стороне сервера.

Управляемая база данных перекладывает операционную нагрузку на провайдера, но не снимает ответственности с клиента. Клиенты по-прежнему отвечают за предоставление ролей и определение объема привилегий. Если вы запускаете PostgreSQL самостоятельно, относитесь к проектированию ролей с наименьшими привилегиями как к первостепенному средству контроля.

Это не просто ошибка базы данных, это проблема границы доверия в облаке. Управляемые базы данных снижают операционную нагрузку, но не устраняют риски, связанные с избыточными привилегиями ролей, скрытой активностью на уровне платформы или взаимодействием функций, которые обходят обычные журналы аудита. Хотя Snowflake устранила эту цепочку, более широкий урок заключается в том, что «управляемый» не означает «отсутствие невидимых рисков».

Будьте в курсе ландшафта угроз вместе с Varonis Threat Labs.

О чём эта статья

Ещё в разделе «Данные и аналитика»

Все →

Ещё от Varonis