التفاف توقيع XML في SAML: لماذا يظل التحقق من التوقيع صعبًا في 2025
يظل SAML العمود الفقري لتسجيل الدخول الموحّد في المؤسسات. يثق مزوّد الخدمة بمزوّد الهوية في تأكيد «هذا المستخدم هو [email protected] وينتمي إلى مجموعة المالية». وتُعبَّر هذه الثقة عبر توقيع XML رقمي. وعندما تختلف منطقية التحقق من التوقيع عن منطقية معالجة التأكيد حول العنصر الموقّع، تكون النتيجة تجاوزًا للمصادقة. تُسمى هذه الفئة التفاف توقيع XML (XSW)، وقد أنتج عام 2025 مجموعة ملحوظة من الثغرات تُظهر أن المكتبات الناضجة ما زالت تخطئ.
1. بدائية XSW
تحتوي استجابة SAML على عنصر <saml:Assertion> أو أكثر. ويتضمن التأكيد الموقّع عنصر <ds:Signature> مع <ds:Reference URI="#assertion-id"> يشير إلى ID التأكيد. على المدقّق الصحيح أن:
- يحدد موقع التوقيع.
- يحل URI المرجع إلى عنصر واحد بالضبط.
- يُجري التقنين (canonicalization) على ذلك العنصر.
- يتحقق من البصمة والتوقيع على الصورة المقنّنة.
- يعالج العنصر نفسه الذي تحقق منه.
يكسر XSW الخطوة الخامسة. يُبقي المهاجم التأكيد الموقّع الأصلي سليمًا — فينجح الفحص التشفيري — لكنه يُدخل تأكيدًا ثانيًا يتحكم به ويقرأه التطبيق لاحقًا. وتنقل المتغيرات التوقيع أو تلتف حول عنصر آخر أو تكرّر المعرّفات أو تستغل فروق فضاءات الأسماء والتقنين. التوقيع صالح، لكن الهوية ليست كذلك.
استجابة ملتفّة مبسطة
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="legit">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:Reference URI="#legit">...</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
</ds:Signature>
<saml:Subject><saml:NameID>[email protected]</saml:NameID></saml:Subject>
</saml:Assertion>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="evil">
<saml:Subject><saml:NameID>[email protected]</saml:NameID></saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
المدقّق الذي يجد التوقيع الأول ويتحقق منه على #legit ثم يستخرج أول تأكيد قد يكون آمنًا. أما المدقّق الذي يتحقق من التوقيع لكنه يستخرج التأكيد الأخير، أو يستخدم XPath مختلفًا للتحقق والمعالجة، فهو مصاب. والفجوة بين المسارين هي كامل سطح الهجوم.
2. مجموعة ثغرات 2025
CVE-2025-25292 — فرق المحلّل في ruby-saml
تأثرت مكتبة ruby-saml بتجاوز مصادقة سببه فرق بين REXML وNokogiri. قد تحلّل محاللات XML المختلفة فضاءات الأسماء والكيانات والمعرّفات المكررة بطرق مختلفة. يصنع المهاجم مستندًا يفسّره أحد المحلّلين كتوقيع صالح على التأكيد الأصلي، بينما يختار الآخر تأكيدًا مختلفًا. أُصلحت في ruby-saml 1.12.4 و1.18.0، وتُقيَّم بـ 9.3 في الإشعارات العامة. وثغرة قريبة، CVE-2025-25291، غطّت مسار REXML.
CVE-2025-47949 — تجاوز التوقيع في samlify
سمحت مكتبة Node.js samlify قبل الإصدار 2.10.0 بتجاوز من نوع التفاف التوقيع يمكنه مصادقة المهاجم كهوية أي مستخدم. والتقييم العام 9.9. ويفرض الإصلاح أن يكون التأكيد الموقّع هو التأكيد المستهلك.
CVE-2025-54419 — node-saml
احتوت node-saml حتى 5.0.1 على تجاوز مصادقة أُصلح في 5.1.0 بدرجة CVSS 10.0 في الإشعار. وتعكس الخطورة النشر النموذجي: مزوّد خدمة يقبل تأكيدًا مزوّرًا قد يمنح وصولًا إداريًا دون أي بيانات اعتماد.
CVE-2025-23369 — GitHub Enterprise Server
عانى GitHub Enterprise Server من تحقق تشفيري غير صحيح في مصادقة SAML، أُصلح في 3.12.14 و3.13.10 و3.14.7 و3.15.2 بدرجة CVSS 7.6. تعكس الدرجة الأدنى شروطًا مسبقة ونطاقًا محدودًا، لكن الفئة نفسها: لم يضمن التحقق أن الهوية المصادَق عليها هي الموقّعة.
3. لماذا تستمر المكتبات في الفشل
- محلّلان ومستند واحد. غالبًا يستخدم التحقق من التوقيع حزمة XML مختلفة عن منطق العمل. وأي فرق دلالي — بادئات فضاء الأسماء، توسيع الكيانات، السمات المكررة، المسافات — يصبح بدائية استغلال.
- حل مرجعي متراخٍ. إذا بحثت الشيفرة عن تأكيد بالمعرّف عبر XPath واسع دون اشتراط تطابق واحد بالضبط، يمكن للمهاجم إضافة عنصر ثانٍ بمعرّف مطابق أو ملتبس.
- فجوات التقنين. تقنين XML دقيق. التعليقات وإعلانات فضاء الأسماء وترتيب السمات يجب أن يعالجها الموقّع والمدقّق بالطريقة نفسها.
- تحوير بعد التحقق. بعض الأطر تفكّك الاستجابة إلى كائن بعد التحقق ثم تعيد قراءة الحقول من XML الأصلي. وقد يختلف الكائن عن XML.
4. اختبار تكامل SAML
استخدم مزوّد هوية مختبريًا مضبوطًا ومزوّد خدمة اختباريًا. التقط استجابة صالحة في وكيل اعتراض، ثم جرّب بالترتيب:
- إزالة التوقيع. أزل
<ds:Signature>بالكامل. يجب أن يرفض مزوّد الخدمة الصحيح الاستجابة. - تبديل التأكيد. أبقِ التأكيد الموقّع، وأضف تأكيدًا ثانيًا بـ
NameIDودور مختلفين، وراقب أيّهما يُعتمد. - نقل التوقيع. انقل التوقيع إلى عنصر غلاف أو إلى التأكيد الثاني مع إبقاء URI يشير إلى الأول.
- معرّفات مكررة. أنشئ تأكيدين بالمعرّف نفسه وضع الخبيث أولًا أو أخيرًا.
- حيل التعليقات وفضاءات الأسماء. أدرج تعليقات داخل
NameIDأو غيّر البادئات. بعض المحاللات يزيلها وأخرى لا.
أدوات مثل SAML Raider لـ Burp Suite تؤتمت متغيرات XSW الشائعة. اعتبر كل نتيجة «توقيع صالح» سؤالًا: صالح على أي عنصر، وهل هو العنصر الذي استخدمه التطبيق؟
5. متطلبات دفاعية
- تحقّق واستهلك العقدة نفسها. حلّل المستند مرة واحدة، واختر التأكيد، وتحقّق من التوقيع على تلك العقدة نفسها في الذاكرة، واقرأ سمات الهوية منها. لا تعد التحليل أو الاختيار بعد التحقق.
- اشترط تأكيدًا واحدًا بالضبط. ارفض الاستجابات المتعددة إلا إذا كان الملف الشخصي يدعمها، وارفض قيم
IDالمكررة. - طبّق ملف SAML كاملًا. تحقّق من
AudienceوRecipientوInResponseToوNotBeforeوNotOnOrAfterوعنوان الوجهة. وارفض الاستجابات غير المطلوبة إلا إذا تطلب تدفقك ذلك. - امنع إعادة التشغيل. خزّن معرّفات التأكيد مؤقتًا على الأقل خلال نافذة الصلاحية وارفض إعادة الاستخدام.
- صحّح المكتبة ثم اختبرها. حدّث ruby-saml إلى 1.12.4/1.18.0 أو أحدث، وsamlify إلى 2.10.0 أو أحدث، وnode-saml إلى 5.1.0 أو أحدث، وGitHub Enterprise Server إلى الإصدارات المصححة. ثم شغّل حالات XSW على تكاملك؛ رقم الإصدار ليس دليل أمان.
- استخدم حزمة XML مدعومة. عطّل DTDs والكيانات الخارجية، وحدّد حجم المستند، وارفض البنى غير المتوقعة قبل التحقق.
6. الخاتمة
XSW ليس خلل محلّل مجردًا؛ إنه خلاف بين مكوّنين حول هوية عنصر موقّع. تُظهر ثغرات 2025 في Ruby وNode.js وGitHub Enterprise Server أن النمط يبقى في شيفرة مستخدمة على نطاق واسع وموجّهة للأمان. الإصلاح الدائم بنيوي: تحليل واحد، وتأكيد واحد مختار، وتحقق واحد، ومصدر واحد للحقيقة لسمات الهوية. أي معمارية تتحقق من التوقيع في مكان وتقرأ المستخدم في مكان آخر ستكون عاجلًا أو آجلًا عرضة لمستند يحتوي إجابتين مختلفتين.