samlify SAML AttributeValue XML Injection → Privilege Escalation (CVE-2026-46490)
by Caio Fabrício (BiiTts) · 2026-07-05
- Severity
- High
- CVE
- CVE-2026-46490 / GHSA-34r5-q4jw-r36m
- Category
- web
- Affected product
- samlify (Node.js SAML library)
- Affected versions
- < 2.13.0 (fixed in 2.13.0)
- Disclosed
- 2026-07-05
- Patch status
- patched
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-07 |
| Author / Researcher | Caio Fabrício (BiiTts) |
| CVE / Advisory | CVE-2026-46490 / GHSA-34r5-q4jw-r36m |
| Category | web |
| Severity | High |
| CVSS Score | 8.8 (High) |
| Status | PoC |
| Tags | saml, sso, xml-injection, privilege-escalation, samlify, cwe-91, identity-provider |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | samlify (Node.js SAML library) |
| Versions Affected | < 2.13.0 (fixed in 2.13.0) |
| Language / Platform | JavaScript / Node.js |
| Authentication Required | Yes (low-privileged authenticated user) |
| Network Access Required | No (exploited via own SAML attribute value; no network access needed beyond normal SSO flow) |
Summary
samlify’s template substitution engine (replaceTagsByValue / escapeTag in src/libsaml.ts) only XML-escapes values that are substituted into XML attribute contexts; values substituted into element text context (such as <saml:AttributeValue>{Value}</saml:AttributeValue>) are inserted verbatim. A low-privileged user who controls one of their own profile attributes (e.g. email) can inject raw XML markup that closes the current <saml:Attribute> element and opens a forged one (e.g. role=admin). Because the injection happens before the assertion is signed by the IdP, the forged attribute is covered by a valid signature, and any Service Provider that trusts the IdP will accept the extra attribute as authoritative — resulting in privilege escalation.
Vulnerability Details
Root Cause
The substitution helper escapeTag inspects an optional preceding quote character captured by its regex to decide whether the value is being placed in an attribute context (Name="{Name}", quote present → escaped) or an element-text context (>{Value}<, no quote → not escaped). samlify’s default attribute template places the attribute value in element text:
<saml:Attribute Name="{Name}" ...><saml:AttributeValue ...>{Value}</saml:AttributeValue></saml:Attribute>
Since the value is unescaped there, any </> characters supplied by the user pass straight through into the generated (and subsequently signed) XML.
Attack Vector
- Attacker authenticates as a normal, low-privileged user on a samlify-based Identity Provider.
- Attacker sets a self-controlled profile attribute (e.g.
email) to a crafted value that closes the<saml:AttributeValue>/<saml:Attribute>elements early and injects a new<saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute>element, then reopens a throwaway attribute to keep the template’s trailing tags balanced. - The victim IdP builds the SAML assertion via
createLoginResponse, performing the vulnerable unescaped substitution, and signs the resulting XML — the forgedroleattribute is now inside the signed scope. - The assertion is sent to a Service Provider, which calls
parseLoginResponse; signature validation succeeds (the signature covers the forged content), and the SP extractsrole: "admin"as a trusted attribute.
Impact
Any authenticated user who can influence one of their own SAML attribute values can forge a validly-signed assertion carrying arbitrary additional attributes (role, group membership, entitlement flags, isAdmin, etc.), escalating privileges on every downstream Service Provider that trusts the vulnerable IdP. Standard SAML signature verification provides no protection since the forgery occurs before signing.
Environment / Lab Setup
Target: samlify@2.12.0 (Node.js) acting as both IdP and SP in-process
Attacker: Any host with Node.js; no network access required beyond normal SSO
Tools: Node.js, npm, samlify library, generated IdP/SP keypairs
Proof of Concept
PoC Script
See
poc.jsandsetup.shin this folder.ANALYSIS.mddocuments the substitution regex, the quote-context heuristic, and the upstream patch.
| |
Running the script drives samlify’s own IdP (createLoginResponse, which performs the vulnerable substitution and signs the assertion) and SP (parseLoginResponse, which validates the signature and extracts attributes). The SP ends up trusting role: "admin" — an attribute the attacker forged rather than one the IdP intentionally issued — confirming the signature-valid privilege escalation.
Detection & Indicators of Compromise
Inspect issued/consumed SAML assertions for attribute values containing raw XML markup,
e.g. "</saml:AttributeValue>" or "<saml:Attribute" inside what should be plain-text
attribute values (email, name, etc.), and for assertions containing more
<saml:Attribute> elements than the IdP's configured attribute template defines.
Signs of compromise:
- SAML assertions where a user-controlled attribute (email, display name) contains embedded
<saml:Attribute>/<saml:AttributeValue>markup. - Users holding roles/entitlements (e.g.
role=admin) inconsistent with their provisioned identity records. - Unexpected attribute counts in signed assertions relative to the IdP’s attribute mapping configuration.
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to samlify ≥ 2.13.0, which XML-escapes interpolated values in element-text contexts as well as attribute contexts. |
| Interim mitigation | Validate/normalize user-supplied attribute values server-side before they are used to build assertions; enforce allow-listed attribute schemas at the SP; strip/reject attribute values containing </> characters. |
References
Notes
Mirrored from https://github.com/BiiTts/CVE-2026-46490-samlify-SAML-Attribute-Injection on 2026-07-05.
| |