Ловушка конфигурации: Как Enterprise-решения становятся уязвимостью в SaaS

 · 4 min read

Приняв решение о разделении продукта на Enterprise и SaaS, мы приступили к проектированию облачной архитектуры. На этапе планирования задача казалась прямолинейной: поднять API, настроить контейнеры-воркеры в облаке и просто передавать им код на обработку.

Zero Trust Cloud Config Trap

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

Анатомия уязвимости: RCE через конфигурацию

RCE Vulnerability in Config

В Enterprise-версии логика настройки была максимально гибкой: движок просто считывал файл конфигурации flude_*_config.json непосредственно из репозитория клиента.

Проблема заключалась в том, как именно эти параметры транслировались в настройки парсера под капотом. Наш конвейер опирался на Doxygen, который мы расширили собственными скриптами для обхода багов C++. Для работы этих фильтров движок использует стандартную директиву Doxygen — INPUT_FILTER, которая позволяет выполнять произвольные shell-команды для обрабатываемых файлов.

В изолированном контуре клиента чтение настроек из репозитория было абсолютно безопасным. Но при переносе этой логики в публичное облако (GCP Cloud Run) возник критический вектор для Remote Code Execution (RCE). Если сервер без разбора доверяет файлу flude_*_config.json из стороннего репозитория, злоумышленник может пробросить переопределение параметров парсера. Внедрив команду вида rm -rf / или reverse-shell через подмену INPUT_FILTER, атакующий заставил бы нашу систему легитимно выполнить этот код на наших же серверах.

Zero-Trust Архитектура и Разрыв доверия

Zero-Trust Architecture

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

  1. Отказ от клиентских конфигураций: SaaS-версия больше никогда не читает flude_*_config.json из репозитория пользователя.
  2. Серверные шаблоны: Наш облачный воркер оперирует исключительно жестко зашитыми, версионированными серверными шаблонами Doxyfile и конфигураций.
  3. Безопасная передача параметров: Пользователь через API может передавать только строго ограниченный набор безопасных data-параметров (например, директории для включения или исключения), которые жестко валидируются на стороне Control Plane. Контроль над тем, какие инструменты и фильтры запускаются в контейнере, навсегда передан серверу.

Укрепление периметра: SSRF и Workload Identity

Закрыв дыру с RCE, мы обратили внимание на сетевую безопасность. Клонирование пользовательских репозиториев сервером — это классический риск Server-Side Request Forgery (SSRF). Чтобы злоумышленники не заставили наши воркеры сканировать внутреннюю сеть GCP под видом git-репозитория, мы внедрили строгий Allowlist: клонирование разрешено только по HTTPS и строго с доменов github.com, gitlab.com и bitbucket.org.

Дополнительно, чтобы минимизировать ущерб в случае гипотетического пробития контейнера, мы полностью отказались от статических ключей (Service Account Keys), переведя все воркеры на динамическую аутентификацию через GCP Workload Identity Federation.

Отложенные угрозы: Атака через исходники

Обезопасив конфигурацию и сеть, мы задались следующим вопросом: а что если вектором атаки станет сам исходный код, который мы парсим?

Мы проанализировали несколько весьма специфичных сценариев:

  1. Злоупотребление тегами Doxygen: Злоумышленник может вписать в комментарий к коду штатные теги вроде \include, \verbinclude или \htmlinclude с путем вида ../../../etc/passwd. Они заставят парсер прочитать файл за пределами директории проекта и вставить его содержимое в итоговую документацию, которая затем вернется клиенту. Наша строгая валидация директорий здесь бессильна, так как она проверяет лишь заявленные входные пути, а не то, что Doxygen решит прочесть в процессе парсинга.
  2. Вредоносные симлинки: Если атакующий закоммитит в репозиторий симлинк, указывающий на системные файлы контейнера, и парсер при обходе директорий последует за ним — это еще один способ извлечь данные вне песочницы репозитория.
  3. Отказ в обслуживании (DoS): В нашей системе вызов бинарника Doxygen ограничен 60 секундами, но на родительский процесс flude_engine compile таймаута не было. Специально сконструированный исходник (с бесконечной рекурсией шаблонов или взрывным раскрытием макросов) способен намертво подвесить процесс облачного воркера.

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

Во-первых, архитектурно было неясно, на каком уровне внедрять эти защиты — особенно в свете предстоящего внедрения нативных парсеров. Мы честно зафиксировали этот долг в roadmap проекта в разделе “Input Validation & Sanitization”.

Во-вторых, реальный риск существенно снижался благодаря архитектуре Data Plane. Все воркеры в Cloud Run изолированы через gVisor, а сами контейнеры абсолютно эфемерны. Даже в случае успешной эксплуатации симлинка или тега \include, атакующий прочитал бы файлы из одноразового, стерильного окружения без доступа к соседним задачам или хосту.

Накапливающееся недовольство Doxygen

что инциденты с потенциальным RCE через INPUT_FILTER и уязвимость штатных тегов вроде \include стали не просто проблемами облачной безопасности. Это были очередные тревожные звонки, напомнившие о фундаментальных архитектурных недостатках Doxygen. Негибкость, зависимость от shell-вызовов и низкая производительность делали его обузой для масштабируемого SaaS-решения (да и для архитектуры в целом).

Забегая вперед скажем: мы не стали мириться с этим техническим долгом. Через пару недель, как только мы закончим цикл статей об инфраструктуре SaaS, мы подробно расскажем, почему окончательно уволили Doxygen.

Но до этого нам предстояло решить еще одну насущную облачную проблему. Мы спроектировали безопасный Data Plane, защищенный от хакеров, но теперь нужно было защитить его от банкротства. О том, как мы строили Free-Tier, защищали API от abuse-нагрузок и проектировали биллинг — читайте в следующем эпизоде.