XML Signature Wrapping SAML : pourquoi la validation de signature reste difficile en 2025
SAML reste l'épine dorsale du SSO en entreprise. Un fournisseur de services fait confiance à un fournisseur d'identité pour affirmer « cet utilisateur est [email protected] et appartient au groupe finance ». Cette confiance s'exprime par une signature numérique XML. Lorsque la logique de validation de signature et la logique de traitement de l'assertion ne sont pas d'accord sur l'élément signé, le résultat est un contournement d'authentification. Cette classe de faille s'appelle XML Signature Wrapping (XSW), et 2025 a produit une série remarquable de CVE montrant que des bibliothèques matures se trompent encore.
1. La primitive XSW
Une réponse SAML contient un ou plusieurs éléments <saml:Assertion>. Une assertion signée inclut un <ds:Signature> avec un <ds:Reference URI="#assertion-id"> pointant vers l'ID de l'assertion. Un vérificateur correct doit :
- Localiser la signature.
- Résoudre l'URI de référence vers exactement un élément.
- Canonicaliser cet élément.
- Vérifier le digest et la signature sur la forme canonique.
- Traiter le même élément que celui vérifié.
XSW casse l'étape 5. L'attaquant conserve l'assertion signée d'origine — la vérification cryptographique passe — mais insère une seconde assertion qu'il contrôle et que l'application lit ensuite. Les variantes déplacent la signature, l'enveloppent autour d'un autre élément, dupliquent les ID ou exploitent des différences de namespace et de canonicalisation. La signature est valide ; l'identité ne l'est pas.
Une réponse enveloppée simplifiée
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="legit">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:Reference URI="#legit">...</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
</ds:Signature>
<saml:Subject><saml:NameID>[email protected]</saml:NameID></saml:Subject>
</saml:Assertion>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="evil">
<saml:Subject><saml:NameID>[email protected]</saml:NameID></saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
Un vérificateur qui trouve la première signature, la valide sur #legit puis extrait la première assertion rencontrée peut être sûr. Un vérificateur qui valide la signature mais extrait la dernière assertion, ou qui utilise un XPath différent pour la vérification et le traitement, est vulnérable. L'écart entre ces deux chemins de code constitue toute la surface d'attaque.
2. La série de CVE 2025
CVE-2025-25292 — différentiel d'analyseur dans ruby-saml
La bibliothèque ruby-saml était affectée par un contournement d'authentification dû à un différentiel entre REXML et Nokogiri. Des analyseurs XML différents peuvent résoudre différemment les namespaces, entités et ID dupliqués. L'attaquant fabrique un document que l'un interprète comme une signature valide sur l'assertion d'origine, tandis que l'autre sélectionne une assertion différente. Le correctif est disponible dans ruby-saml 1.12.4 et 1.18.0 ; les avis publics indiquent 9.3. Un problème proche, CVE-2025-25291, couvrait le chemin REXML.
CVE-2025-47949 — contournement de signature dans samlify
La bibliothèque Node.js samlify, avant la version 2.10.0, permettait un contournement de type signature wrapping capable d'authentifier un attaquant comme n'importe quel utilisateur. Le score public est de 9.9. Le correctif impose que l'assertion signée soit celle qui est consommée.
CVE-2025-54419 — node-saml
node-saml jusqu'à 5.0.1 contenait un contournement d'authentification corrigé en 5.1.0, avec un CVSS de 10.0 dans l'avis. La gravité reflète le déploiement typique : un fournisseur de services qui accepte une assertion forgée peut accorder un accès administratif sans identifiant.
CVE-2025-23369 — GitHub Enterprise Server
GitHub Enterprise Server présentait une vérification cryptographique incorrecte dans l'authentification SAML, corrigée en 3.12.14, 3.13.10, 3.14.7 et 3.15.2, avec un CVSS de 7.6. Le score plus bas reflète des préconditions et une portée limitée, mais la classe est identique : la vérification ne garantissait pas que l'identité authentifiée était celle qui avait été signée.
3. Pourquoi les bibliothèques échouent encore
- Deux analyseurs, un document. La vérification de signature utilise souvent une pile XML différente de la logique métier. Toute différence sémantique — préfixes de namespace, expansion d'entités, attributs dupliqués, espaces — devient une primitive d'exploitation.
- Résolution de référence laxiste. Si le code recherche une assertion par ID avec un XPath large sans exiger exactement une correspondance, l'attaquant peut ajouter un second élément avec un ID identique ou ambigu.
- Lacunes de canonicalisation. La canonicalisation XML est subtile. Commentaires, déclarations de namespace et ordre des attributs doivent être traités de façon identique par le signataire et le vérificateur.
- Mutation après validation. Certains frameworks désérialisent la réponse en objet après vérification puis relisent des champs dans le XML d'origine. L'objet et le XML peuvent diverger.
4. Tester une intégration SAML
Utilisez un IdP de laboratoire contrôlé et un fournisseur de services de test. Capturez une réponse valide dans un proxy d'interception, puis tentez dans l'ordre :
- Suppression de signature. Retirez complètement
<ds:Signature>. Un SP correct rejette la réponse. - Échange d'assertion. Gardez l'assertion signée, ajoutez une seconde assertion avec un autre
NameIDet rôle, observez laquelle est honorée. - Déplacement de signature. Déplacez la signature dans un élément enveloppant ou dans la seconde assertion en gardant l'URI pointée vers la première.
- ID dupliqués. Créez deux assertions avec le même
IDet placez la malveillante en premier ou en dernier. - Commentaires et namespaces. Insérez des commentaires dans le
NameIDou changez les préfixes. Certains analyseurs les normalisent, d'autres non.
Des outils comme SAML Raider pour Burp Suite automatisent les variantes XSW courantes. Considérez chaque résultat « signature valide » comme une question : valide sur quel élément, et est-ce celui utilisé par l'application ?
5. Exigences défensives
- Vérifiez et consommez le même nœud. Analysez le document une fois, sélectionnez l'assertion, vérifiez la signature sur ce nœud exact en mémoire, et lisez les attributs d'identité depuis ce même nœud. Ne ré-analysez jamais après vérification.
- Exigez exactement une assertion. Rejetez les réponses multiples sauf si le profil les autorise, et rejetez les
IDdupliqués. - Appliquez tout le profil SAML. Validez
Audience,Recipient,InResponseTo,NotBefore,NotOnOrAfteret l'URL de destination. Rejetez les réponses non sollicitées sauf besoin explicite. - Empêchez le rejeu. Mettez en cache les ID d'assertion au moins pendant la fenêtre de validité et rejetez la réutilisation.
- Corrigez puis testez. Passez ruby-saml à 1.12.4/1.18.0 ou plus, samlify à 2.10.0 ou plus, node-saml à 5.1.0 ou plus, et GitHub Enterprise Server aux versions corrigées. Exécutez ensuite les cas XSW contre votre intégration ; un numéro de version ne prouve pas la sécurité.
- Utilisez une pile XML maintenue. Désactivez les DTD et entités externes, limitez la taille des documents et rejetez les structures inattendues avant la vérification.
6. Conclusion
XSW n'est pas un bug d'analyseur abstrait ; c'est un désaccord entre deux composants sur l'identité d'un élément signé. Les CVE 2025 en Ruby, Node.js et GitHub Enterprise Server montrent que le schéma survit dans du code largement utilisé et axé sur la sécurité. Le correctif durable est structurel : une analyse, une assertion sélectionnée, une vérification et une source unique de vérité pour les attributs d'identité. Toute architecture qui valide une signature à un endroit et lit l'utilisateur à un autre finira par être vulnérable à un document contenant deux réponses différentes.