С потоком: перехват теневой зависимости в GCP Dataflow между арендаторами

Фото: ZuluZulu (Pixabay) — https://pixabay.com/photos/man-smoking-monochrome-cigarette-1278713/

Источник: Varonis•

С потоком: перехват теневой зависимости в GCP Dataflow между арендаторами

Varonis Threat Labs обнаружили уязвимость между арендаторами в Google Cloud Dataflow, которая потенциально позволяла злоумышленникам использовать предсказуемые имена бакетов и добиваться выполнения удаленного кода (RCE) на воркерах Dataflow другой организации без какого-либо доступа к проекту жертвы. При определенных условиях атака начиналась всего лишь с идентификатора проекта жертвы в GCP.

После нашего исследования Dataflow Rider мы изучили Apache Beam, SDK с открытым исходным исходным кодом, лежащий в основе Dataflow, и обнаружили, что при определенных условиях злоумышленники могут запускать код в проектах, к которым у них нет прямого доступа. Соединив предсказуемую схему именования бакетов с флагом, который уже используется во многих заданиях Dataflow, они могли использовать ту же учетную службу, что была скомпрометирована в Dataflow Rider, не требуя никакого исходного доступа к среде жертвы.

Жертва должна была запустить конвейер, созданный с помощью Python SDK от Beam, и это должно было быть первое задание Dataflow для проекта в данном регионе, использующее бакет промежуточного хранения по умолчанию. С тех пор Google устранила эту проблему, выпустив глобальные исправления, которые защищают даже старые или зафиксированные версии SDK.

Предсказуемый бакет промежуточного хранения

Внутри gcsio.py мы узнали, как Beam определяет, где находится бакет промежуточного хранения конвейера, если конвейер не задает его явно.

Если в конвейере не настроен явный параметр --staging_location, Beam вычисляет имя бакета по умолчанию детерминированным образом на основе идентификатора проекта и региона:

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

Затем это предсказуемое имя используется для поиска бакета и его создания, если он не существует:

Имена бакетов GCS составляют глобальное пространство имен. Только один проект в мире может владеть dataflow-staging-<region>-<hash> в каждый момент времени. Кто первый создал, тот и победил. Это классический сквоттинг бакетов: злоумышленник заранее вычисляет имя будущего бакета промежуточного хранения жертвы, создает его в своем собственном проекте и предоставляет доступ AllUsers к нему.

Когда жертва позже запускает свое первое задание Dataflow в этом регионе без выделенного бакета промежуточного хранения, get_or_create_default_gcs_bucket() находит бакет злоумышленника с таким же именем и просто повторно использует его. В отличие от Java SDK от Beam, который уже проверял, принадлежит ли бакет проекту, Python SDK никогда не проверял, кто им владеет.

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

Засквотированный бакет сам по себе — это лишь плацдарм. Вторая составляющая, которая превращает его в выполнение кода, — это то, что множество конвейеров загружают в этот бакет без задней мысли: --save_main_session.

От угадываемого бакета к выполнению кода

Каждое задание Dataflow — это просто скрипт на Python. Когда этот скрипт полагается на глобальное состояние, такое как импортированные модули, вспомогательные функции или константы, определенные на верхнем уровне, воркерам, выполняющим конвейер, также необходима копия этого состояния.

В момент нашего исследования Dataflow доверял промежуточным артефактам, извлеченным из хранилища, без их проверки, как мы видели в Dataflow Rider (с тех пор Google добавила проверку целостности, описанную ниже). Однако этот артефакт не является YAML-шаблоном или UDF, логику которых конвейер просто интерпретирует. Это необработанный поток pickle, а десериализация потока pickle в Python не просто восстанавливает данные, она может выполнять код. Любой объект, определяющий __reduce__, может решать, что запускается в момент его десериализации:

Соедините предсказуемое имя бакета и сериализованную сессию вместе, и бакет, который злоумышленник захватил до того, как жертва запустила какое-либо задание, станет механизмом доставки для произвольного выполнения кода на воркерах Dataflow этой жертвы.

Объединение в цепочку: захват, наблюдение и подмена

Если объединить сквоттинг бакетов и выполнение кода, получится путь атаки, который выглядит следующим образом:

  • Подготовьте вредоносный бакет промежуточного хранения в организации злоумышленника. Для целевого идентификатора проекта и одного или нескольких вероятных регионов предварительно вычислите dataflow-staging-{region}-{md5(project_id)} и создайте каждый бакет в проекте, принадлежащем злоумышленнику, с отключенным предотвращением публичного доступа и предоставленными правами чтения/записи для AllUsers.
  • Сгенерируйте вредоносную полезную нагрузку pickle в организации злоумышленника. Создайте объект Python, метод __reduce__ которого обращается к серверу метаданных экземпляра, извлекает учетные данные и токен доступа сервисного аккаунта воркера и эксфильтрует их на конечную точку, контролируемую злоумышленником.
  • Дождитесь, пока жертва запустит задание --save_main_session без явно настроенного бакета промежуточного хранения в регионе, где проект еще не создал свой бакет по умолчанию. Dataflow создает рабочую папку задания внутри засквотированного злоумышленником бакета и загружает в нее легитимный pickled_main_session.
  • Обнаружение и подмена. Злоумышленник опрашивает бакет на предмет появления новых папок с префиксом задания, и в момент появления pickled_main_session удаляет его и повторно загружает вредоносный pickle из шага 2, опережая воркеры, которые собираются запуститься.
  • Ожидание выполнения. Каждый воркер, загружающийся для этого задания, включая новые воркеры, запущенные за счет автоскейлинга, скачивает и десериализует подмененный файл, запуская код злоумышленника с правами сервисного аккаунта воркера Dataflow. По умолчанию этот сервисный аккаунт может иметь широкие привилегии, например быть стандартным сервисным аккаунтом Compute Engine.

Видео хода атаки

В видео ниже показан полный процесс атаки: от сквоттинга предсказуемого бакета промежуточного хранения до подмены сериализованной сессии и выполнения кода на воркерах Dataflow.

Влияние

Влияние здесь зависит от привилегий сервисного аккаунта выполнения задания, но может включать:

  • Кражу учетных данных: эксфильтрацию OAuth-токена сервисного аккаунта воркера Dataflow через конечную точку метаданных экземпляра.
  • Выполнение кода: произвольное выполнение кода на каждом воркере Dataflow для данного задания, включая воркеры, запущенные позже посредством автоскейлинга.
  • Эскалацию привилегий / латеральное перемещение: использование украденного токена для перехода к другим ресурсам GCP, к которым у сервисного аккаунта есть доступ.
  • Манипуляцию с данными или их эксфильтрацию: поскольку полезная нагрузка выполняется внутри того же процесса воркера, который обрабатывает данные конвейера, а также потому, что контролируемый злоумышленником бакет промежуточного хранения может подвергать риску промежуточные артефакты и результаты заданий, записанные туда для эксфильтрации.

Исправление

Мы сообщили о нашем обнаружении захвата бакетов (bucket-squatting) в проект Apache Beam, и в июне 2026 года было выпущено исправление. Мы представили наше исследование и выводы на конференции DEF CON 2026 в Cloud Village. Компания Google действовала быстро, оперативно и профессионально на протяжении всего процесса раскрытия информации, а команда Google Cloud VRP поблагодарила нас за отчет, сотрудничество и помощь в обеспечении безопасности Google.

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

Уровень 1: Проверка владельца в Beam Python SDK

Google внесла вклад в виде проверки на стороне клиента для Apache Beam Python SDK (apache/beam PR #39008, выпущено в Beam 2.75.0). Она добавляет явную проверку владельца перед тем, как Beam начнет доверять бакету, который он не создавал самостоятельно.

Функция get_or_create_default_gcs_bucket() теперь вызывает _validate_bucket_project() для любого предварительно существующего бакета, обнаруженного по предсказуемому имени по умолчанию, сопоставляя ID проекта вызывающего абонента с номером проекта через Cloud Resource Manager API и сравнивая его с фактическим номером проекта-владельца бакета.

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

Это закрывает вектор атаки путем захвата бакетов на стороне клиента для заданий, использующих Beam 2.75.0 или более поздней версии.

Уровень 2: Принудительное применение на стороне сервера в сервисе Dataflow

Google также развернула аналогичную проверку непосредственно в бэкенде сервиса Dataflow. При отправке любого задания с использованием бакета для развертывания по умолчанию плоскость управления Dataflow самостоятельно проверяет, что бакет принадлежит отправляющему проекту, и отклоняет задание с ошибкой FAILED_PRECONDITION, если он принадлежит другому проекту. Поскольку это правило применяется на стороне сервера, клиенты, использующие более старые или фиксированные версии Beam SDK, также защищены от захвата бакета по умолчанию без необходимости обновления.

Уровень 3: Сквозные проверки целостности для развернутых артефактов

Наконец, Google внедрила проверку целостности SHA-256 для развернутых файлов в Dataflow и Apache Beam. Во время развертывания SDK вычисляет хеши SHA-256 для развернутых артефактов, включая pickled_main_session, и записывает их. Воркеры Dataflow проверяют дайджест каждого загруженного артефакта при запуске и прерывают работу, если какой-либо файл в GCS был изменен или подменен после развертывания. Это останавливает гонку «обнаружение и подмена» из шага 4 атаки.

Две важные вещи, о которых следует помнить:

  • Явно указанные бакеты остаются на ваше усмотрение. Проверки владельца применяются к автоматически выбираемому бакету для развертывания по умолчанию. Бакеты, которые вы настраиваете явно с помощью --staging_location или --temp_location, намеренно могут находиться в общем или централизованном проекте, поэтому убедитесь, что этот проект и разрешения для его бакетов надежно защищены.
  • Pickle остается pickle. Параметр --save_main_session по-прежнему сериализует вашу сессию с помощью pickle, что по дизайну может выполнять код. Проверка целостности теперь предотвращает подмену развернутых файлов после загрузки, но бакет для развертывания по-прежнему является частью цепочки поставок вашего кода. Относитесь к правам записи в него так же, как к правам записи в исходный код.

Заключительные мысли

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

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

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

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

Все →

Ещё от Varonis