
ثغرة XSS مخزنة في Bloomberg Memray عبر بيانات وصفية لسطر الأوامر غير مُهرَّبة
ثغرة XSS مخزنة في Bloomberg Memray عبر بيانات سطر أوامر غير مُهرَّبة
اكتشفت هذه الثغرة أثناء مراجعتي لـ Memray، أداة بلومبيرغ لتوصيف الذاكرة في Python، مع سؤال بسيط في ذهني:
هل يمكن لبيانات وقت التشغيل التي يتحكم بها المهاجم أن تعبر إلى مخرجات التقرير المعروضة في المتصفح بطريقة غير آمنة؟
في هذه الحالة، كان الجواب نعم.
كان الخلل في مسار توليد تقارير HTML في Memray، حيث كانت بيانات سطر الأوامر تُعرض في تقرير يُفتح في المتصفح دون تهريب. حوّل هذا حقلاً تشغيلياً إلى نقطة استقبال HTML قابلة للتنفيذ، وأصبح في النهاية CVE-2026-32722.
المشروع: Memray على GitHub
النشرة الأمنية: GHSA-r5pr-887v-m2w9 /CVE-2026-32722
attacker-controlled argv → metadata.command_line → HTML template sink without escaping → raw markup in generated report → browser-side JavaScript execution
Memray هي أداة لتوصيف الذاكرة في Python.
تضيف أدوات قياس إلى عملية Python، وتسجل سلوك تخصيص الذاكرة، وتنتج تقارير تساعد المطورين على فهم:
بعض هذه التقارير تُصدر بصيغة HTML وتُفتح في أحد المتصفحات.
وهذا يجعل توليد التقارير حداً أمنياً حقيقياً.
السؤال الجوهري ليس ما إذا كان Memray "أداة محلية".
السؤال الجوهري هو ما إذا كانت البيانات التي يتحكم بها المهاجم يمكنها أن تعبر إلى المخرجات المعروضة في المتصفح بطريقة غير آمنة.
في هذه الحالة، كان ذلك ممكناً.
كثير من الناس يقللون من شأن أدوات المطورين.
وهذا خطأ.
بمجرد أن تقوم أداة بـ:
فإنها تتحمل مخاطر ترميز المخرجات نفسها التي تتحملها تطبيقات الويب.
كان هذا جوهر المشكلة هنا.
لم يكن هذا الخلل في منطق التوصيف. لم يكن في تتبع تخصيص الذاكرة. لم يكن في معالجة الذاكرة الأصلية.
بل كان فشلاً كلاسيكياً في حدود الثقة:
هذا كافٍ لإنشاء ثغرة حقيقية.
لم أتعامل مع Memray بالفازينغ العشوائي لخيارات سطر الأوامر أو بمطاردة الأعطال.
كان النهج الأقوى هو تحديد السطح الأمني الأعلى احتمالاً أولاً.
بالنسبة إلى Memray، كان ذلك توليد تقارير HTML.
لماذا؟
لأن مخرجات HTML تُدخل نقطة استقبال في المتصفح، ونقاط استقبال المتصفح تحوّل أخطاء البيانات الوصفية العادية إلى مشاكل أمنية إذا:
هذا بالضبط ما حدث هنا.
يمكن اختزال الخلل في سطرين.
في:
def get_render_environment() -> jinja2.Environment:
loader = jinja2.PackageLoader("memray.reporters")
env = jinja2.Environment(loader=loader)
يتم إنشاء بيئة Jinja دون تهريب تلقائي (autoescape).
ثم في:
Command line: <code>{{ metadata.command_line }}</code><br>
يُعرض metadata.command_line مباشرة داخل HTML.
هذه هي الثغرة برمتها.
لأن metadata.command_line متأثر بالمهاجم.
يسجل Memray سطر الأوامر المستخدم لتشغيل البرنامج الذي يخضع للتوصيف.
وهذا يعني أن القيم التي يتحكم بها المستخدم من argv تُحفظ كبيانات وصفية وتُدرج لاحقاً في التقرير.
لذا فإن سلسلة الاستغلال واضحة ومباشرة:
metadata.command_lineوهذا يحوّل البيانات الوصفية إلى محتوى قابل للتنفيذ في المتصفح.
التمييز المهم هو التنفيذ.
العديد من الأخطاء تُنتج HTML غير سليم. وهذا وحده لا يكفي.
هنا، لم يكن المحتوى الذي يتحكم به المهاجم مجرد شيء ظاهر في مصدر الصفحة. بل فسّره المتصفح على أنه HTML نشِط ونفّذه كـ JavaScript.
هذا هو الفرق بين:
لذا لم يكن السؤال:
«هل يمكن أن يظهر HTML في التقرير؟»
السؤال الحقيقي كان:
«هل يمكن أن يصبح HTML الذي يتحكم به المهاجم قابلاً للتنفيذ عند فتح التقرير؟»
وكان الجواب نعم.
استخدمت أداة إعادة الإنتاج الأولية سكربتاً صريحاً بالإضافة إلى وسيط يتحكم به المهاجم:
cat > victim.py <<'PY'
x = [b"A" * 1024 for _ in range(1000)]
print("done")
PY
python -m memray run -o poc.bin victim.py ''
python -m memray flamegraph -o poc.html poc.bin
احتوى HTML المُولَّد على ترميز خام يتحكم به المهاجم:
Command line: <code>~/memray/src/memray/__main__.py run -o poc.bin victim.py </code><br>
أدى فتح التقرير المُولَّد أو تحديثه إلى تنفيذ JavaScript.
أثبت ذلك الادعاء الجوهري:
لاحقاً، أثناء الإفصاح المنسق، بسّط مشرف المشروع أداة إعادة الإنتاج أكثر:
python -m memray run -o poc.bin -c '# '
هذه النسخة أفضل لأنها تعزل الحد القابل للاختراق بشكل أكثر مباشرة:
كانت الحمولة بسيطة عن قصد:
الأمر لا يتعلق بحمولات مبهرجة.
إنها مسبار تنفيذ نظيف لأن:
بالنسبة إلى هذه الفئة من الأخطاء، هذا كافٍ.
كان سيكفي نوع تقرير واحد لتبرير الإبلاغ عن المشكلة.
لكنني أردت معرفة ما إذا كان هذا الخلل معزولاً أم بنيوياً.
أكدت السلوك نفسه في:
--no-webكان ذلك مهماً لسببين.
أظهر أن نقطة الاستقبال القابلة للاختراق أُعيد استخدامها عبر مخرجات HTML متعددة.
أثبت أن الخلل لا يعتمد على موارد خارجية مستضافة عبر CDN.
ما زال --no-web يعيد إنتاج المشكلة، مما يعني أن المشكلة كانت في HTML المُولَّد ومعالجة القوالب داخل Memray نفسه، وليس في سلوك JS البعيد.
وهذا جعل الحالة أقوى بكثير.
كان الشاغل الرئيسي لمشرف المشروع هو التحكم العملي للمهاجم.
وهذا عادل.
هذه ليست من نوع المشاكل التي يصطدم فيها مهاجم بعيد غير مصادَق بنقطة نهاية HTTP مكشوفة ويحقق تأثيراً فورياً.
شرط الاستغلال أضيق:
لذا كان التصنيف الواقعي هو خطورة منخفضة.
وهذا لا يجعله خللاً ضعيفاً.
الخطورة تتعلق بشروط الاستغلال والتأثير المحتمل. أما الصحة فتتعلق بما إذا كانت المشكلة حقيقية.
كانت هذه المشكلة حقيقية بوضوح:
ولهذا السبب حصلت على CVE في النهاية.
كان الإصلاح في حده الأدنى وصحيحاً.
غيّر مشرف المشروع:
{{ metadata.command_line }}
إلى:
{{ metadata.command_line|e }}
هذا هو الإصلاح الصحيح لأنه يعالج نقطة الاستقبال القابلة للاختراق مباشرة.
بدلاً من إخراج ترميز خام مثل:
يُخرج القالب الآن نصاً مهرباً:
<img src=x onerror=alert(1)>
يحافظ هذا على القيمة المعلوماتية لحقل سطر الأوامر مع إزالة مسار التنفيذ في المتصفح.
كما راجع مشرف المشروع بقية سياق القالب وخلص إلى أن:
|tojsonلذا حُصرت المشكلة بشكل صحيح في metadata.command_line.
هذا بالضبط نوع مراجعة الإصلاح الذي تريده في إفصاح حقيقي.
تم الإبلاغ عن هذه الثغرة بشكل خاص عبر GitHub Security Advisories.
مشرفو المشروع:
تم تخصيص المعرف التالي للمشكلة: CVE-2026-32722
الدرس الأساسي هنا بسيط:
البيانات الوصفية ليست موثوقة تلقائياً لمجرد أنها تبدو تشغيلية.
لا شيء من ذلك يهم بمجرد أن يعبر المحتوى الذي يتحكم به المهاجم إلى المخرجات المعروضة في المتصفح دون تهريب.
في اللحظة التي يُصدر فيها أي أداة HTML، يجب التعامل معها كتطبيق ينتج HTML.
هذا هو الاستنتاج الحقيقي.
لم تكن هذه الثغرة حول حمولة ذكية.
كانت حول تحديد الحد الصحيح.
أخذ Memray بيانات وصفية لسطر الأوامر متأثرة بالمهاجم وعرضها في HTML مُولَّد دون تهريبها. وقام المتصفح بالباقي.
ولهذا أصبحت هذه CVE-2026-32722.
أُصلحت في Memray 1.19.2.
