
Spring Boot와 Quarkus에서 Apache Camel camel-coap 컨슈머의 오류 메시지 노출을 시연하고 수정 사항 검증까지 포함하는 CVE-2026-101259용 실행 가능한 개념 증명(PoC) 재현기입니다.
Apache Camel 취약점에 대한 실행 가능한 개념 증명(PoC) 재현 도구로, CoAP 컨슈머가 라우트가 완료된 후 응답을 생성하지 못했을 때 CamelCoapResource가 발생한 예외를 포착하여 그 메시지를 5.00 Internal Server Error의 페이로드로 CoAP 클라이언트에 다시 전송하는 문제를 다룹니다:
} catch (Exception e) {
cexchange.respond(ResponseCode.INTERNAL_SERVER_ERROR, e.getMessage());
}
| 런타임 | 디렉터리 | 스택 |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-coap 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
컨슈머는 최종 메시지 본문을 byte[] 페이로드로, 그리고 CamelCoapMaxAge 및
CamelCoapETag 헤더를 해당 타입으로 변환하는데, 이는 라우트가 반환된 후에 수행되며 따라서
라우트의 오류 핸들러 외부에서 이루어집니다. 라우트 내의 onException은 이를 잡을 수 없습니다.
왜냐하면 실패는 라우트가 아니라 컨슈머에서 발생하기 때문입니다.
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) Converting the body to the byte[] payload fails once the route has completed
CoAP GET coap://127.0.0.1:5683/reading
response code : 5.00 (INTERNAL_SERVER_ERROR)
response payload : Error during type conversion from type: com.example.FailingBackendStream to the
required type: byte[] with value com.example.FailingBackendStream@5eede7d7 due to
java.io.IOException: connection to prod-db.internal:5432 reset while streaming
sensor_readings for node edge-gateway-07.dc2.internal
2) The CamelCoapMaxAge header cannot be converted to its type
CoAP GET coap://127.0.0.1:5683/reading-maxage
response code : 5.00 (INTERNAL_SERVER_ERROR)
response payload : Error during type conversion from type: java.lang.String to the required type:
java.lang.Long with value not-a-number due to java.lang.NumberFormatException:
For input string: "not-a-number"
3) A route that simply fails
CoAP GET coap://127.0.0.1:5683/reading-failing
response code : 2.05 (CONTENT)
response payload : partially built response
What a remote CoAP client learned from these responses:
the backend host and port, out of the cause's message : true
class names of the cause : true
the source and target types of the conversion : true
the string form of the offending value : true
a failed route answered with a SUCCESS code : true
>>> PROVEN: ...
이러한 실패의 메시지는 변환 중인 값의 클래스 이름을 명시하고, 값의 문자열 형태를 담고 있으며, 근본 원인의 클래스 이름과 메시지로 끝난다. 따라서 원격 CoAP 클라이언트는 클래스 및 패키지 이름, 값의 문자열 형태, 그리고 근본 원인이 담고 있는 모든 것 — 실제로는 백엔드의 경로, 호스트 이름, 연결 세부 정보를 포함할 수 있음 — 을 읽을 수 있다. 소비자는 이를 억제할 옵션이 없었으며, 그러한 목적을 위해 muteException을 제공하는 HTTP 소비자와는 달랐다.
프로브 3은 이 문제의 나머지 절반이다. 실패한 라우트에서 발생한 예외는 전혀 전송되지 않았다. 소비자는 교환이 실패했는지 확인하지 않았고, 라우트 실패에 대해 2.05 Content와 그 시점에 메시지가 담고 있던 본문을 그대로 응답했다 — 실패한 라우트에 대한 성공 코드였다.
이 재현기가 반드시 맞춰야 했던 한 가지: 변환기가 존재하지 않는 본문으로는 충분하지 않다. 해당 경우에
getBody(byte[].class)는 예외를 던지는 대신null을 반환하고, 그러면 소비자는 빈 페이로드와 함께2.05로 응답한다 — 아무런 노출도 없다. 변환이 사용 불가능한 것이 아니라 실패해야 하며, 이것이 프로브 1이 읽기 도중에 예외를 던지는 스트림 본문을 사용하는 이유다. 헤더 프로브는 다른 방식으로, 즉 잘못된 형태의 값을 통해 실패한다.
이 노출에 도달하려면 원격 클라이언트가 완료된 응답을 변환 불가능하게 만들 수 있는 라우트가 필요하며, 이는 라우트가 어떻게 작성되었는지에 달려 있다. 여기서 인증되는 것은 아무것도 없으며, 클라이언트가 보내는 것은 일반 CoAP GET뿐이다.
| 속성 | 값 |
|---|---|
| 구성 요소 | camel-coap (CamelCoapResource) |
| CWE | CWE-209 (민감한 정보를 포함하는 오류 메시지 생성) |
| 심각도 | 중간 |
| 공격 벡터 | 완료된 응답을 구성할 수 없는 소비자에 대한 CoAP 요청 — byte[]로의 변환에 실패하는 본문, 또는 변환에 실패하는 CamelCoapMaxAge / CamelCoapETag 헤더 |
| 영향 | 원격 CoAP 클라이언트가 클래스 및 패키지 이름, 값의 문자열 형태, 근본 원인의 메시지를 읽음 — 실제로는 경로, 호스트 이름, 백엔드 연결 세부 정보 |
| 영향받는 버전 | 4.0.0부터 4.18.5 이전, 4.19.0부터 4.22.2 이전 |
| 수정된 버전 | 4.18.5, 4.22.2, 4.23.0 |
| JIRA | CAMEL-25087 |
| 크레딧 | Zhixi "Jace" Sun, 독립 보안 연구원 |
권고문: https://camel.apache.org/security/CVE-2026-101259.html
소비자는 muteException 옵션을 얻게 되었으며 — coap, coaps, coap+tcp, coaps+tcp 엔드포인트 모두에서 동일하게 — 이 옵션은 **세 개의 수정된 릴리스 라인 모두에서 기본값이 true**이므로 업그레이드만으로 충분하며 구성 변경이 필요 없다. 소비자는 여전히 5.00 Internal Server Error로 응답하지만 빈 페이로드와 함께 응답하며, 이제 소비자의 예외 핸들러를 통해 예외를 보고하므로 이를 무음 처리해도 운영자로부터 숨겨지지 않는다.
또한 교환이 실패했는지 확인하고, 동일한 규칙에 따라 라우트 실패에 5.00 Internal Server Error로 응답하며, 이전에는 2.05 Content로 응답했던 부분이다.
main에서 apache/camel#26994 (2245feac)로 수정되었고, camel-4.22.x에는 #27046 (868d5604)으로, camel-4.18.x에는 #27048 (aaf5bbf8)으로 백포트되었다.
camel.version을 4.22.2로 설정하고 다시 실행한다. 4.22.1 → 4.22.2에 대해 검증됨:
1) ... response code: 5.00 (INTERNAL_SERVER_ERROR) response payload : <empty>
2) ... response code: 5.00 (INTERNAL_SERVER_ERROR) response payload : <empty>
3) ... response code: 5.00 (INTERNAL_SERVER_ERROR) response payload : <empty>
>>> NOT reproduced: the error payload was empty.
Probe 3은 코드도 2.05 Content에서 5.00 Internal Server Error로 변경합니다. 이제 라우트 실패는 실패로 응답됩니다.
라우트가 완료되면 응답이 항상 구성될 수 있도록 하세요. coap 엔드포인트에서 소비하는 각 라우트의 마지막 단계에서 본문을 byte[] 또는 String으로 변환하고(convertBodyTo), CamelCoapMaxAge 및 CamelCoapETag 헤더에는 각자의 타입에 해당하는 값만 지정하며, 실패를 처리됨으로 표시하고 응답 본문을 명시적으로 설정하는 onException 핸들러를 추가하세요. 그러면 변환 실패가 컨슈머가 아니라 라우트 내부에서 발생하고 대체됩니다.
이 저장소는 교육 및 방어 목적으로 게시됩니다. Apache Camel 사용자가 취약점을 이해하고, 영향을 받는지 확인하며, 업그레이드로 문제가 해결되는지 검증할 수 있도록 돕기 위한 것입니다. 소유하거나 운영하지 않는 시스템에 이 자료를 사용하지 마세요.