# samlify: XML Injection in AttributeValue Allows Privilege Escalation in Signed SAML Assertions

samlify escapes placeholders only in attribute context, so a user-supplied email injects extra <saml:Attribute> elements into the IdP-signed assertion, granting role=admin.

- Payout: Research
- Severity: high
- Program: samlify
- Found by: RootUp
- Disclosed: 2026-05-21
- CVE: CVE-2026-46490
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-34r5-q4jw-r36m
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-34r5-q4jw-r36m
- Tags: area:web domain:web impact:integrity impact:privesc kind:research platform:ghsa sev:high tech:nodejs tech:saml technique:source-review vuln:auth-bypass vuln:privilege-escalation

**Target:** SAML IdP login response template substitution (libsaml.replaceTagsByValue)
**Root cause:** replaceTagsByValue() only escapes placeholders preceded by a quote (attribute context); placeholders in element text such as <saml:AttributeValue>{attrUserX}</saml:AttributeValue> are inserted raw, so closing tags and new <saml:Attribute> elements pass through and get signed.
**Steps:**
1. Configure IdP loginResponseTemplate with an attribute whose valueTag maps to user-controlled data (e.g. user.injection).
2. Set the user value to `safe</saml:AttributeValue></saml:Attribute><saml:Attribute Name="role" ...><saml:AttributeValue ...>admin</saml:AttributeValue></saml:Attribute><saml:Attribute Name="injection" ...><saml:AttributeValue ...>safe`.
3. Call idp.createLoginResponse() so the tampered assertion is built and signed by the IdP.
4. Base64-decode the SAMLResponse and observe the injected <saml:Attribute Name="role">admin</saml:Attribute> inside the signed assertion.
5. Call sp.parseLoginResponse(); extract.attributes contains the injected role=admin attribute.
**How found:** Reporter noticed template substitution escapes only quote-preceded placeholders, then checked that attribute builder inserts placeholders into element text, making tag-breakout possible.
**Exploitation:** A normal user controls an attribute value (email/name) that the IdP places into element text. By injecting closing tags and a new <saml:Attribute Name="role">admin</saml:Attribute>, the IdP signs the tampered assertion; the SP parses the injected attribute as trusted and grants elevated privileges where attributes drive authorization.
**Escalation:** Injected role/group attributes are trusted by SPs using SAML attributes for authorization, yielding privilege escalation to admin.
**Insight:** When auditing template-based XML/SAML builders, check whether escaping is context-aware: placeholders in element text (not just quoted attributes) are the injection point, and anything the IdP signs becomes trusted by the SP.

## Proof of concept (copied exactly from the original report)

**Injection payload building forged role attribute**

```
const injection = [
    'safe',
    '</saml:AttributeValue></saml:Attribute>',
    '<saml:Attribute Name="role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">',
    '<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">admin</saml:AttributeValue>',
    '</saml:Attribute>',
    '<saml:Attribute Name="injection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">',
    '<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">safe'
  ].join('');
```

