
Preuve de concept Docker exécutable pour CVE-2026-71569, un contournement de la protection SSRF dans la validation des webhooks camel-a2a d'Apache Camel, avec des sondes et une vérification du correctif.
Reproducteur de preuve de concept exécutable pour la vulnérabilité Apache Camel où WebhookUrlValidator
classait un hôte de webhook de notification push à deux endroits qui ne concordaient pas :
fc00::/7 ("fc", "fd") ;isLoopbackAddress,
isAnyLocalAddress, isLinkLocalAddress et isSiteLocalAddress.Ces prédicats ne couvrent pas le même périmètre : isSiteLocalAddress signale le bloc obsolète fec0::/10,
et non les adresses locales uniques fc00::/7 qui l'ont remplacé, et le JDK ne fournit aucun prédicat pour
celles-ci. La même adresse était donc acceptée ou rejetée selon la façon dont elle était écrite.
| Runtime | Répertoire | 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) |
Le nom d'hôte doit se résoudre dans fc00::/7, ce que le fichier compose organise avec une entrée extra_hosts
(Docker l'écrit dans le /etc/hosts du conteneur au démarrage) — exécutez donc ceci via Docker, et non avec un
simple 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
Sortie sur une build affectée (identique sur les deux runtimes) :
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: ...
Les sondes 1 et 2 sont la même adresse, et les verdicts divergent. La sonde 3 présente la même incohérence dans
l'autre sens : la pré-vérification de chaîne correspondait à l'hôte brut sans d'abord établir qu'il s'agissait d'un
littéral IP, de sorte que tout nom commençant simplement par fc ou fd était refusé d'emblée — fcm.googleapis.com, un
point de terminaison push parfaitement ordinaire, entre autres. Les sondes 4 et 5 sont des témoins montrant que le garde-fou fonctionne là où il
a été écrit correctement.
Chaque sonde passe allowLocal=false, qui est la valeur par défaut (allowLocalWebhookUrls=false) et la
configuration dans laquelle le garde-fou est censé tenir.
L'URL de webhook arrive sous la forme TaskPushNotificationConfig.url depuis l'appelant qui enregistre la
notification push, elle est donc contrôlée par l'attaquant. PushNotificationDispatcher envoie ensuite la notification
push à cette adresse, avec tout en-tête Authorization porté par la même configuration —
dont le schéma et les identifiants sont fournis par l'appelant qui enregistre.
La réponse n'est pas relayée à cet appelant, ce qui produit donc une requête HTTPS sortante aveugle
depuis le processus Camel vers une adresse interne que le garde-fou était censé refuser. Atteindre un service
de cette manière exige en outre que le nom se résolve dans fc00::/7 dans l'environnement cible, qu'un
service HTTPS écoute sur cette adresse, et que la poignée de main TLS réussisse pour le nom demandé —
c'est pourquoi la sévérité est évaluée à moyenne.
| Propriété | Valeur |
|---|---|
| Composant | camel-a2a (WebhookUrlValidator) |
| CWE | CWE-918 (Server-Side Request Forgery) |
| Sévérité | Moyenne |
| Vecteur d'attaque | Une URL de webhook de notification push dont le nom d'hôte se résout dans fc00::/7 |
| Impact | Une requête HTTPS sortante aveugle, avec l'en-tête Authorization configuré, vers une adresse interne que le garde-fou aurait dû refuser |
| Versions affectées | À partir de 4.21.0 avant 4.22.2 |
| Versions corrigées | 4.22.2, 4.23.0 |
| JIRA | CAMEL-24371 |
| Crédit | 2122295973 at qq dot com, signalé via l'ASF Security Team |
Avis de sécurité : https://camel.apache.org/security/CVE-2026-71569.html
Les deux chemins passent désormais par InetAddress.getByName, qui analyse un littéral IPv6 entre crochets sans toucher au
DNS, et classifient l'adresse résultante à partir de ses octets bruts avec un classificateur partagé — ainsi un hôte est
jugé selon l'adresse qu'il désigne plutôt que selon la façon dont il est écrit. Ce classificateur reconnaît fc00::/7
et l'espace d'adresses partagé 100.64.0.0/10, ainsi que les adresses compatibles IPv4, NAT64 (64:ff9b::/96) et 6to4
(2002::/16) dont l'adresse IPv4 intégrée n'est pas routable globalement.
Corrigé sur main dans apache/camel#25406 (54041cce), rétroporté
vers camel-4.22.x dans #26583 (c4a5c5fc).
Définissez camel.version sur 4.22.2 et relancez. Vérifié sur 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
Les deux orthographes obtiennent désormais le même verdict, avec le même message, et le faux positif de la sonde 3 a également disparu.
Placez une authentification devant le chemin de configuration des notifications push A2A afin que l'URL du webhook ne provienne pas d'un appelant non fiable, et refusez l'egress depuis le processus Camel vers fc00::/7 au niveau réseau. Lorsque le déploiement n'a pas besoin d'IPv6, démarrer la JVM avec -Djava.net.preferIPv4Stack=true supprime la classe d'adresses sur laquelle repose la faille. Notez que allowLocalWebhookUrls=false est déjà la valeur par défaut et ne comble pas à lui seul la faille — la sonde 2 ci-dessus s'exécute avec ce réglage.
Ce dépôt est publié à des fins éducatives et défensives : pour aider les utilisateurs d'Apache Camel à comprendre la vulnérabilité, à vérifier s'ils sont affectés et à confirmer que la mise à niveau la résout. N'utilisez pas ce contenu contre des systèmes que vous ne possédez pas ou n'exploitez pas.