
Reproducer for CVE-2026-43866 — Apache Camel camel-jms forged DefaultExchangeHolder bypass of the CVE-2026-40860 deserialization filter (Exchange-state injection)
Este projeto demonstra CVE-2026-43866, um bypass da correção do CVE-2026-40860 nos componentes camel-jms (e camel-sjms / família JMS) do Apache Camel. CVE-2026-40860 adicionou uma lista de permissão de classes pós-desserialização (java.**;javax.**;org.apache.camel.**;!*) a payloads JMS ObjectMessage recebidos. Mas org.apache.camel.support.DefaultExchangeHolder reside no namespace org.apache.camel.** incluído na lista de permissão, portanto um ObjectMessage cujo objeto de nível superior é um DefaultExchangeHolder passa na verificação. O lado receptor então chama DefaultExchangeHolder.unmarshal() nele sem exigir transferExchange — escrevendo todos os campos não nulos do holder no Exchange roteado (corpo, cabeçalhos IN/OUT, propriedades do exchange, variáveis, id do exchange, exceção). Um atacante que pode publicar um ObjectMessage pode, portanto, injetar estado arbitrário do Exchange usando apenas tipos java.* universalmente confiáveis — nenhuma cadeia de gadgets de desserialização necessária — para manipular roteamento, cabeçalhos, propriedades e tratamento de erros.
Advisory: https://camel.apache.org/security/CVE-2026-43866.html
// JmsBinding.extractBodyFromJms(...) - affected 4.18.2
if (message instanceof ObjectMessage objectMessage) {
Object payload = objectMessage.getObject();
checkDeserializedClass(payload); // CVE-2026-40860 allow-list: java.**;javax.**;org.apache.camel.**;!*
if (payload instanceof DefaultExchangeHolder holder) { // <-- DefaultExchangeHolder is org.apache.camel.** -> passes
DefaultExchangeHolder.unmarshal(exchange, holder); // <-- writes forged state into the Exchange; NO transferExchange gate
Map<String, Object> jmsHeaders = extractHeadersFromJms(message, exchange);
exchange.getIn().getHeaders().putAll(jmsHeaders);
return exchange.getIn().getBody();
} else {
return payload;
}
}
A assimetria: o lado remetente controla a criação de ObjectMessage/transferExchange, mas o lado receptor desserializa qualquer DefaultExchangeHolder que desserializar. A correção (4.14.8 / 4.18.3 / 4.21.0) adiciona uma nova opção objectMessageEnabled (security = "insecure:serialization", padrão false) — um ObjectMessage recebido não é mais desserializado a menos que explicitamente ativado, então um holder forjado nunca pode alcançar unmarshal(). (Esta é uma mudança que quebra a compatibilidade para rotas que dependem de ObjectMessage / transferExchange.)
Uma lista de permissão do provedor JMS não ajuda. Este PoC configura o cliente ActiveMQ com um
trustedPackagesrealista e restritotrustedPackages = [java, javax, org.apache.camel]— a entradaorg.apache.camelé exatamente o que uma implantação usandotransferExchangelegítimo deve confiar. O holder forjado ainda passa, porque ele próprio é umDefaultExchangeHoldercujos campos são todos tiposjava.*confiáveis, indistinguível de um legítimo.
from("jms:queue:cve") // mapJmsMessage defaults to true; transferExchange NOT set
.process(exchange -> { /* observes the injected body / headers / properties */ });
A vítima é o consumidor Camel JMS. O atacante é qualquer produtor que possa publicar na fila. Ambos se comunicam com um broker real Apache ActiveMQ Artemis no Docker.
CVE-2026-43866/
├── pom.xml # camel-jms 4.18.2 + activemq-client 6.2.4 (NO gadget library)
├── Dockerfile # runs the app (no --add-opens; no gadget)
├── docker-compose.yml # Artemis broker (quay.io) + the reproducer app
├── README.md
└── src/main/
├── java/com/example/
│ ├── Application.java
│ ├── JmsConfig.java # OpenWire ConnectionFactory (trustedPackages incl. org.apache.camel) + jms component
│ ├── VictimRoute.java # victim: from("jms:queue:cve"); records what the route observed
│ ├── CapturedState.java
│ ├── ForgedHolderFactory.java # builds a DefaultExchangeHolder via the public marshal() API
│ └── ExploitController.java # attacker: publishes the forged holder as an ObjectMessage
└── resources/
└── application.properties
mvn clean package -DskipTests
docker compose up -d --build
curl -s http://localhost:8080/exploit/attack
# -> Published a forged DefaultExchangeHolder as a JMS ObjectMessage to queue 'cve'.
# ...
# body = INJECTED-BODY-... (injected: true)
# header = pwned-header-... (injected: true)
# property = pwned-property-... (injected: true)
#
# >>> Exchange-state injection proof — attacker controlled body+header+property: true
A rota foi executada com um corpo, cabeçalho e propriedade que nunca foram definidos — todos fornecidos pelo holder forjado do atacante.
docker compose down
Qualquer consumidor Camel JMS (camel-jms, camel-sjms, camel-sjms2, camel-amqp, camel-activemq, camel-activemq6) com mapJmsMessage=true (o padrão), lendo de um destino no qual um atacante pode publicar. transferExchange não precisa estar habilitado no consumidor.
mapJmsMessage=true (padrão), em uma versão afetada.ObjectMessage cujo payload é um DefaultExchangeHolder.transferExchange, significa que o provedor já confia em org.apache.camel. Nenhuma biblioteca de gadgets é necessária.Atualize para 4.14.8 / 4.18.3 / 4.21.0 (CAMEL-23373 / CAMEL-23409). O tratamento de ObjectMessage JMS está desabilitado por padrão através da nova opção objectMessageEnabled; ative-a apenas para destinos alimentados exclusivamente por produtores confiáveis.
Até a atualização:
ObjectMessage para redes não confiáveis.DefaultExchangeHolder).Este reprodutor é fornecido apenas para pesquisa de segurança e testes autorizados, para uma vulnerabilidade divulgada publicamente e corrigida. Não o utilize contra sistemas sem permissão explícita.
| Propriedade | Valor |
|---|
| Componentes | camel-jms (+ camel-sjms, camel-sjms2, camel-amqp, camel-activemq, camel-activemq6) |
| Classe Afetada | org.apache.camel.component.jms.JmsBinding#extractBodyFromJms → DefaultExchangeHolder.unmarshal |
| CWE | CWE-502 (Desserialização de Dados Não Confiáveis) + CWE-20 (Validação de Entrada Imprópria) |
| Impacto | Injeção de estado do Exchange: corpo, cabeçalhos, propriedades, variáveis, exceção controlados pelo atacante |
| Natureza | Bypass da correção de filtro de classe do CVE-2026-40860 (não uma falha nela) |
| Versões Afetadas | De 3.0.0 antes de 4.14.8, de 4.15.0 antes de 4.18.3, de 4.19.0 antes de 4.21.0 |
| Versões Corrigidas | 4.14.8, 4.18.3, 4.21.0 |
| JIRA | CAMEL-23373 (jms), CAMEL-23409 (sjms) |
| Repórter | gaorenyusi |