
Proof-of-concept Docker eseguibile per CVE-2026-71569, un bypass della protezione SSRF nella validazione dei webhook di Apache Camel camel-a2a, con probe e verifica della correzione.
Riproduttore proof-of-concept eseguibile per la vulnerabilità di Apache Camel in cui WebhookUrlValidator
classificava un host webhook di notifica push in due punti che non concordavano:
fc00::/7 ("fc", "fd");isLoopbackAddress,
isAnyLocalAddress, isLinkLocalAddress e isSiteLocalAddress.Quei predicati non coprono lo stesso terreno: isSiteLocalAddress segnala il blocco deprecato fec0::/10,
non gli indirizzi locali univoci fc00::/7 che lo hanno sostituito, e il JDK non fornisce alcun predicato per
questi ultimi. Lo stesso indirizzo veniva quindi accettato o rifiutato a seconda di come era scritto.
| Runtime | Directory | Stack |
|---|---|---|
| Camel Spring Boot | camel-spring-boot/ | Spring Boot 3.5.13 + camel-a2a 4.22.1 |
| Camel Quarkus | camel-quarkus/ | Camel Quarkus 3.39.0 (Quarkus 3.39.0, Camel 4.22.0) |
Il nome host deve risolversi in fc00::/7, cosa che il file compose organizza con una voce extra_hosts
(Docker la scrive nel file /etc/hosts del container all'avvio) — quindi eseguilo tramite Docker, non con un
semplice java -jar:
cd camel-spring-boot # or: cd camel-quarkus
mvn clean package
docker compose up -d --build
until curl -sf http://localhost:8080/exploit/ready >/dev/null; do sleep 1; done
curl -s http://localhost:8080/exploit/attack
docker compose down
Output su una build interessata (identico su entrambi i runtime):
Resolution in this environment: internal-service.example -> fd00:0:0:0:0:0:0:1
1) The unique local address as an IP LITERAL
url: https://[fd00::1]/push-notification
verdict: REFUSED - Webhook URL must not point to private/internal IPv6 ranges (SSRF protection): [fd00::1]
2) The SAME address reached through a host name
url: https://internal-service.example/push-notification
verdict: ACCEPTED, resolved to fd00:0:0:0:0:0:0:1
3) A legitimate host name that merely BEGINS with "fc" (the bug's other direction)
url: https://fcm.googleapis.com/push-notification
verdict: REFUSED - Webhook URL must not point to private/internal IPv6 ranges (SSRF protection): fcm.googleapis.com
4) CONTROL - an IPv4 private address, which isSiteLocalAddress does cover
verdict: REFUSED - ... site-local/private address ...
5) CONTROL - a link-local IPv6 literal
verdict: REFUSED - ... private/internal IPv6 ranges ...
>>> PROVEN: ...
Le sonde 1 e 2 sono lo stesso indirizzo, e i verdetti sono discordanti. La sonda 3 è la stessa incoerenza nella
direzione opposta: il pre-controllo sulla stringa corrispondeva all'host grezzo senza prima stabilire che fosse un
letterale IP, quindi qualsiasi nome che iniziava semplicemente con fc o fd veniva rifiutato a priori — fcm.googleapis.com, un
endpoint push del tutto ordinario, tra gli altri. Le sonde 4 e 5 sono controlli che mostrano la guardia funzionare dove
era stata scritta correttamente.
Ogni sonda passa allowLocal=false, che è il valore predefinito (allowLocalWebhookUrls=false) e la
configurazione in cui ci si aspetta che la guardia tenga.
L'URL del webhook arriva come TaskPushNotificationConfig.url dal chiamante che registra la notifica
push, quindi è controllato dall'attaccante. PushNotificationDispatcher invia poi la notifica push
a quell'indirizzo, insieme a qualsiasi header Authorization trasportato nella stessa configurazione —
il cui schema e le cui credenziali sono forniti dal chiamante che registra.
La risposta non viene ritrasmessa a quel chiamante, quindi ciò che si ottiene è una richiesta HTTPS
in uscita cieca dal processo Camel verso un indirizzo interno che la guardia era stata scritta per rifiutare. Raggiungere un servizio
in quel modo richiede inoltre che il nome si risolva in fc00::/7 nell'ambiente di destinazione, che un
servizio HTTPS sia in ascolto su quell'indirizzo e che l'handshake TLS riesca per il nome richiesto
— motivo per cui la gravità è valutata media.
| Proprietà | Valore |
|---|---|
| Componente | camel-a2a (WebhookUrlValidator) |
| CWE | CWE-918 (Server-Side Request Forgery) |
| Gravità | Media |
| Vettore di attacco | Un URL di webhook per notifiche push il cui nome host si risolve in fc00::/7 |
| Impatto | Una richiesta HTTPS in uscita cieca, con l'header Authorization configurato, verso un indirizzo interno che la guardia avrebbe dovuto rifiutare |
| Versioni interessate | Dalla 4.21.0 precedenti alla 4.22.2 |
| Versioni corrette | 4.22.2, 4.23.0 |
| JIRA | CAMEL-24371 |
| Credito | 2122295973 at qq dot com, segnalato tramite l'ASF Security Team |
Advisory: https://camel.apache.org/security/CVE-2026-71569.html
Entrambi i percorsi ora passano attraverso InetAddress.getByName, che analizza un letterale IPv6 tra parentesi quadre senza toccare
il DNS, e classificano l'indirizzo risultante dai suoi byte grezzi con un unico classificatore condiviso — così un host è
giudicato dall'indirizzo che denota anziché da come è stato scritto. Quel classificatore riconosce fc00::/7
e lo spazio di indirizzi condiviso 100.64.0.0/10, nonché gli indirizzi IPv4-compatibili, NAT64 (64:ff9b::/96) e 6to4
(2002::/16) il cui indirizzo IPv4 incorporato non è instradabile globalmente.
Corretto su main in apache/camel#25406 (54041cce), backportato
su camel-4.22.x in #26583 (c4a5c5fc).
Imposta camel.version su 4.22.2 ed eseguilo di nuovo. Verificato su 4.22.1 → 4.22.2:
1) The unique local address as an IP LITERAL
verdict: REFUSED - Webhook URL must not point to a unique local address (SSRF protection): [fd00::1]
2) The SAME address reached through a host name
verdict: REFUSED - Webhook URL must not point to a unique local address (SSRF protection): internal-service.example
3) A legitimate host name that merely BEGINS with "fc"
verdict: ACCEPTED, resolved to 216.239.38.55
Entrambe le grafie ora ottengono lo stesso verdetto, con lo stesso messaggio, e anche il falso positivo nella probe 3 è scomparso.
Collocare l'autenticazione davanti al percorso di configurazione delle notifiche push A2A in modo che l'URL del webhook non arrivi da un chiamante non attendibile, e negare l'egress dal processo Camel verso fc00::/7 a livello di rete. Nei casi in cui il deployment non abbia bisogno di IPv6, avviare la JVM con -Djava.net.preferIPv4Stack=true elimina la classe di indirizzi su cui si basa la lacuna. Si noti che allowLocalWebhookUrls=false è già l'impostazione predefinita e non chiude da sola la lacuna — la probe 2 sopra viene eseguita con essa attiva.
Questo repository è pubblicato per scopi educativi e difensivi: per aiutare gli utenti di Apache Camel a comprendere la vulnerabilità, verificare se sono interessati e confermare che l'aggiornamento la risolve. Non utilizzare questo materiale contro sistemi di cui non si è proprietari o che non si gestiscono.