
استشارة أمنية: برمجة نصية عبر المواقع مخزنة في رسائل الوكيل تؤدي إلى سرقة رمز الجلسة (openclaw-dashboard)
العنوان: XSS مخزنة في لوحة تحكم OpenClaw عبر حقل الجلسة lastMessage معرّف CVE: CVE-2026-66421
https://github.com/tugcantopaloglu/openclaw-dashboard
يعرض جدول الجلسات في الصفحة الرئيسية للوحة التحكم آخر رسالة لكل جلسة وكيل. يُؤخذ هذا النص من سجل محادثة الوكيل ويُكتب في الصفحة عبر innerHTML دون أي تهريب. OpenClaw هي بوابة وكيل متعددة القنوات، لذا يمكن أن يأتي نص الرسالة من أي شخص قادر على مخاطبة الوكيل، مثل مجموعة دردشة أو خطاف ويب. أي رسالة تحتوي على حمولة HTML تعمل كسكريبت في متصفح المسؤول فور فتحه للوحة التحكم.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:H/SI:H/SA:Nالثغرة موجودة منذ أول إصدار عام. العرض القابل للاستغلال موجود في أقدم وسم (v1.1.0) وفي كل commit لاحق، بما في ذلك main الحالي (d6198d0). لم يتم إصلاحها وقت كتابة هذا التقرير.
المهاجم هو طرف قادر على إرسال رسالة تصل إلى سجل جلسة الوكيل. لا يحتاج إلى حساب في لوحة التحكم. في نشر نموذجي لـ OpenClaw، يشمل ذلك أعضاء قنوات الدردشة المتصلة وأي مصدر يصل إلى الوكيل عبر خطاف ويب، لذا يكون المهاجم غالباً خارج حدود الثقة الخاصة بالمشغّل.
تُخزَّن الحمولة في السجل وتُعرض كلما اطّلع المسؤول على صفحة الجلسات. تلك الصفحة هي العرض الافتراضي وتتجدد عبر مؤقت، لذا فإن التفاعل الوحيد المطلوب هو أن يحمّل المسؤول لوحة التحكم التي يستخدمها عادةً. يعكس AT:P الشرط الوحيد الذي يعتمد عليه المهاجم: وهو أن النشر يوجّه رسائله إلى جلسة تعرضها لوحة التحكم.
الخطوة 1: يُستخرج نص الرسالة من السجل. يقرأ الخادم أحدث رسالة ويعيد نصها مقتطعاً إلى 80 حرفاً دون أي تعقيم:
// server.js:474-481
if (typeof msg.content === 'string') {
text = msg.content;
} else if (Array.isArray(msg.content)) {
for (const b of msg.content) {
if (b.type === 'text' && b.text) { text = b.text; break; }
}
}
if (text) return text.replace(/\n/g, ' ').substring(0, 80);
الخطوة 2: يُكشَف النص عبر واجهة برمجة تطبيقات الجلسات. يضع getSessionsJson النص في حقل lastMessage لكل جلسة:
// server.js:538
lastMessage: getLastMessage(s.sessionId || key),
يعيد GET /api/sessions هذه المصفوفة إلى المتصفح.
الخطوة 3: يُكتب في DOM دون تهريب. يبني جدول الجلسات كل صف عبر دمج السلاسل النصية ويعيّنه باستخدام innerHTML:
// index.html:3758
const lastMsg = s.lastMessage ? s.lastMessage.substring(0, 60) + (s.lastMessage.length > 60 ? '…' : '') : '';
// index.html:3773
<div class="table-cell" style="..." onclick="toggleSessionExpand('${escapedKey}', event)">${lastMsg}</div>
يُوضع lastMsg مباشرة في سلسلة HTML. لا توجد أي خطوة ترميز في هذا المسار بأكمله.
يدرج الجدول نفسه s.label (index.html:3770) وs.key (index.html:3767) في ترميز الصف بالطريقة نفسها. لا يوجد لهذين الحقلين حد أقصى قدره 60 حرفاً، لذا يمنحان المهاجم القادر على التأثير فيهما مساحة أكبر لحمولة.
تسمح سياسة أمان المحتوى في server.js:298 بمعالجات الأحداث المضمّنة (script-src 'self' 'unsafe-inline')، لذا يعمل معالج onerror.
بوصفك طرفاً قادراً على مراسلة الوكيل، أرسل رسالة نصها حمولة قصيرة تناسب حد الـ 60 حرفاً، على سبيل المثال:
يبلغ طول هذا النص 56 حرفاً ويصل إلى سجل جلسة الوكيل مثل أي رسالة عادية.
تأكد من أن الخادم يعيد الحمولة دون تهريب:
curl -H "Authorization: Bearer ADMIN_TOKEN" http://TARGET:7000/api/sessions
يحتوي حقل lastMessage للجلسة المتأثرة على سلسلة `` الخام.
يفتح المسؤول لوحة التحكم. صفحة الجلسات هي العرض الافتراضي، لذا لا حاجة إلى أي تنقّل إضافي. يُعرض الصف، ويحلّل المتصفح وسم ``، ويعمل معالج onerror، وتقرأ الحمولة رمز جلسة المسؤول.
استبدال جسم console.log باستدعاء fetch إلى نقطة نهاية موثّقة بنفس الأصل يتيح للحمولة التصرف بصلاحيات المستخدم المسجّل دخوله.
تنفيذ سكريبت في نطاق لوحة التحكم بصلاحيات المستخدم المسجّل دخوله، ويمكن الوصول إليه من أي شخص لا يحتاج سوى إرسال رسالة إلى الوكيل. يمكن للحمولة قراءة رمز الجلسة واستدعاء نقاط نهاية موثّقة، بما في ذلك تلك التي تعدّل ملفات تعليمات الوكيل نفسها وإعدادات OpenClaw. ولأنها تنطلق على الصفحة الافتراضية، لا يضطر المسؤول إلى القيام بأي إجراء غير معتاد.
lastMessage وlabel وkey قبل إدراجها في ترميز الصف، أو أنشئ الخلايا باستخدام textContent بدلاً من سلسلة HTML.innerHTML.'unsafe-inline' من script-src بحيث يتعذر تنفيذ الترميز المحقون حتى لو فاتت خطوة ترميز.