تفتح Google Analytics الصبح تلاقي الزيارات مضروبة في عشرة، والسلة عليها زحمة، وتقول خلاص المتجر طار. بعدها تبص على المبيعات: صفر. دي مش حملة ناجحة، دي بوتات بتلعب في السلة. القصة الجاية من متجر ووردبريس إلى متجر إلكتروني كامل: منتجات وسلة ودفع وشحن ومخزون وطلبات. تتوسع بإضافات لبوابات الدفع المحلية والشحن، وتمنحك ملكية كاملة لمتجرك…">WooCommerce حقيقي، والحل فيها ينفع لأي متجر عندك.
العَرَض: نمو وهمي بلا أي مبيعات
لاحظ فريق المتجر ثلاث علامات معًا: ارتفاع حاد في الجلسات والمستخدمين الجدد، وتفاعل شبه معدوم، وتركّز غريب للزيارات حول صفحة السلة.
السجلات (Logs) على السيرفر كشفت الصورة الحقيقية؛ طلبات متكررة بهذا الشكل، وأحيانًا بفارق ثانية أو ثانيتين فقط:
GET /shop/example-product/?add-to-cart=1234
GET /basket/
ومعرّف المتصفح (User-Agent) يبدو عاديًا تمامًا:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/150.0.0.0 Safari/537.36
هذه الطلبات ليست بريئة؛ كل ?add-to-cart ينشئ جلسة سلة ويشغّل PHP وقاعدة البيانات، ويتجاوز الكاش غالبًا. على استضافة مشتركة، هذا كافٍ لإبطاء المتجر على العملاء الحقيقيين.
لماذا فشلت الحلول التقليدية؟
عند حصر عناوين IP التي أرسلت هذه الطلبات بنفس البصمة، كانت النتيجة 8,096 عنوانًا مختلفًا، أغلبها ظهر مرة أو مرتين فقط. هذا هو أسلوب البروكسيات المتناوبة (Rotating Proxies).
مقارنة سريعة
حظر الهوية مقابل حماية العملية
| المعيار | حظر IP / User-Agent | إثبات موقّع + Redis |
|---|---|---|
| ضد البروكسيات المتناوبة | ضعيف | فعّال |
| خطر حظر عملاء حقيقيين | مرتفع | منخفض |
| يعمل مع كاش الصفحات | نعم | عبر REST |
| يسمح لوكلاء التسوق | غالبًا لا | مسار منفصل |
| سهولة التطبيق | سهل | يحتاج كود |
| الحل المغري | لماذا لا يكفي |
|---|---|
| حظر عناوين IP | العناوين تتغير أسرع من أي قائمة حظر |
| — | — |
| تحديد معدل الطلبات (Rate Limiting) لكل IP | كل عنوان يرسل طلبًا أو اثنين فقط |
| حظر الـ User-Agent | مجرد نص يمكن تزويره، وقد تحظر متصفحات حقيقية |
| حظر دولة أو شبكة استضافة | يضرب عملاء حقيقيين ويُبقي البوتات |
| حظر الطلبات بلا Referer | إشارة ضعيفة، والكثير من الزوار الحقيقيين بلا Referer |
القاعدة التي خرج بها الفريق: كل هذه إشارات، ولا شيء منها يصلح حدًا أمنيًا. السؤال الصحيح ليس «هل هذا الـ IP بوت؟» بل: «هل يحق لهذا الطلب تنفيذ عملية مكلفة تغيّر حالة المتجر؟»
الخطوة الأولى: البوتات المعروفة وترتيب الهوكات
جزء من الزيارات جاء من بوتات تعلن عن نفسها مثل MJ12bot وSERankingBacklinksBot، وهذه يمكن حجبها بالاسم. لكن ظهرت مشكلة أدق: التوقيت.
تعالج WooCommerce طلبات ?add-to-cart القديمة داخل الهوك wp_loaded. فإذا جاء فحص الحماية بعد ذلك، يكون المنتج قد أُضيف إلى السلة فعلًا حتى لو اكتُشف البوت. الحل تشغيل الفحص على الهوك نفسه بأولوية مبكرة:
add_action(
'wp_loaded',
array( __CLASS__, 'observe_request' ),
1
);
الأولوية 1 تضمن أن الفحص يسبق معالجة السلة. بعد التعديل أصبحت البوتات المعروفة تتلقى 403، والمتصفح العادي يكمل طبيعيًا.
الخطوة الثانية: احمِ العملية لا الهوية
للزيارات المتنكرة في هيئة متصفح، بنى الفريق طبقة تحمي عملية «الإضافة إلى السلة عبر الرابط» نفسها. الفكرة: الزائر الحقيقي يتصفح المتجر أولًا، فيحصل على إثبات قصير العمر في كوكي، ولا تُقبل ?add-to-cart بدونه.
يُوقَّع الإثبات بـ توقيع HMAC باستخدام مفاتيح الأمان في ووردبريس:
$issued_at = time();
$nonce = wp_generate_password( 20, false, false );
$payload = 'v1.' . $issued_at . '.' . $nonce;
$signature = hash_hmac(
'sha256',
$payload,
wp_salt( 'auth' )
);
يُحفظ الإثبات في كوكي بخصائص HttpOnly وSecure وSameSite=Lax، وتنتهي صلاحيته بعد 5 دقائق. قبل أن تعالج WooCommerce الطلب، يتحقق الحارس من التوقيع والعمر:
| الطلب | النتيجة |
|---|---|
?add-to-cart مع إثبات صالح | يمر إلى WooCommerce |
| — | — |
?add-to-cart بدون إثبات أو بإثبات منتهٍ | يُرفض بـ 403 |
هذا ليس اختبارًا لكون الزائر إنسانًا، فبوت متطور يستطيع الحصول على الإثبات. لكنه يمنع الطلبات «العمياء» التي تأتي من العدم، ويرفع تكلفة الهجوم كثيرًا.
مثال مبسّط تبني عليه
الكود التالي من إعدادنا في ArabCMS، يوضح الفكرة نفسها في إضافة صغيرة أو في functions.php لقالب فرعي. جرّبه على بيئة الاختبار (Staging) أولًا:
<?php
// Issue a short-lived signed proof cookie on normal storefront views.
add_action( 'template_redirect', function () {
if ( is_admin() || isset( $_GET['add-to-cart'] ) || acms_cart_proof_valid() ) {
return;
}
$payload = 'v1.' . time() . '.' . wp_generate_password( 20, false, false );
$sig = hash_hmac( 'sha256', $payload, wp_salt( 'auth' ) );
setcookie( 'acms_cart_proof', $payload . '.' . $sig, array(
'expires' => time() + 300,
'path' => COOKIEPATH,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
) );
} );
function acms_cart_proof_valid() {
if ( empty( $_COOKIE['acms_cart_proof'] ) ) {
return false;
}
$parts = explode( '.', sanitize_text_field( wp_unslash( $_COOKIE['acms_cart_proof'] ) ) );
if ( count( $parts ) !== 4 ) {
return false;
}
list( $v, $issued_at, $nonce, $sig ) = $parts;
if ( time() - (int) $issued_at > 300 ) {
return false;
}
$expected = hash_hmac( 'sha256', "$v.$issued_at.$nonce", wp_salt( 'auth' ) );
return hash_equals( $expected, $sig );
}
// Run before WooCommerce processes legacy ?add-to-cart (also on wp_loaded).
add_action( 'wp_loaded', function () {
if ( isset( $_GET['add-to-cart'] ) && ! acms_cart_proof_valid() ) {
wp_die( 'Forbidden', '', array( 'response' => 403 ) );
}
}, 1 );
تنبيه: روابط «أضف إلى السلة» المباشرة التي ترسلها في حملات واتساب أو إعلانات فيسبوك ستُرفض لأن الزائر يصل بدون إثبات. إن كنت تعتمد عليها، فبدلًا من
403حوّل الزائر إلى صفحة المنتج نفسها بدون المعاملadd-to-cart.
مشكلة الكاش: الصفحة لا تشغّل PHP
مع تفعيل كاش الصفحات الكاملة، تُقدَّم الصفحة من الكاش دون تشغيل كود PHP، فلا يُصدَر الإثبات أصلًا. حل الفريق كان نقطة REST API مستقلة لا تُخزَّن مؤقتًا، يستدعيها المتجر بـ JavaScript لإصدار الكوكي:
/wp-json/wccg/v1/proof
وترد بـ:
{"issued":true}
بهذا يبقى كاش الصفحات يعمل، ويحصل الزائر الحقيقي على الإثبات في الخلفية.
الطبقة الثالثة: عدّادات Redis
المتجر يستخدم Redis أصلًا كـ كاش كائني (Object Cache)، فاستُخدمت دوال wp_cache_* العادية كعدّادات رخيصة لرصد:
- عدد عمليات الإضافة للسلة في الدقيقة على مستوى المتجر.
- الطلبات بإثبات صالح مقابل الطلبات بدونه.
- عدد الطلبات المرفوضة.
- سرعة إصدار الإثباتات.
فإذا تعلّم البوت طلب الإثبات أولًا ثم إضافة المنتج، تكشفه هذه العدّادات عبر السلوك والسرعة، وتصبح هي الطبقة التالية.
لا تحظر وكلاء التسوق الذكيين
اعتبار مهم في المقال الأصلي: وكلاء الذكاء الاصطناعي التي تتسوق نيابة عن المستخدمين تزداد انتشارًا. الحظر الشامل لكل ما هو آلي سيقطعها أيضًا.
الحل الذي اتبعه الفريق هو فصل مسارات الثقة: المتصفح يمر عبر الإثبات الموقّع، بينما تمر الوكلاء الموثوقة عبر واجهات برمجية مخصصة مثل REST وMCP مستثناة من حارس السلة. الزحف لقراءة بيانات عامة يختلف عن تعديل سلة، ويجب أن تعكس سياسة الأمان هذا الفرق.
النتيجة بالأرقام
بعد التفعيل، من عينة 36 طلب add-to-cart:
| العدد | |
|---|---|
مرفوض (403) | 35 |
| — | — |
مسموح (302) | 1 |
| طلبات بصمة Chrome/150 المرفوضة | 30 من 30 |
استمر تبديل عناوين IP، لكنه لم يعد يفيد المهاجم، لأن الـ IP لم يعد هو أساس القرار.
في Elementor: لو صفحات منتجاتك مبنية بـ Elementor Pro وزر الإضافة للسلة يعمل عبر AJAX (
wc-ajax=add_to_cart)، فالحارس السابق لا يلمسه لأنه يستهدف رابط?add-to-cartالقديم فقط. افحص سجلات السيرفر أولًا لتعرف أي مسار يستهدفه البوت عندك.
اقرأ أيضًا
الأسئلة الشائعة
س: كيف أعرف أن متجري يتعرض لهجوم على السلة؟
ج: ابحث في سجلات السيرفر عن تكرار ?add-to-cart مع ارتفاع الجلسات وانعدام المبيعات. لوحات الاستضافة مثل cPanel توفر Raw Access Logs لهذا الغرض.
س: هل تكفي إضافة CAPTCHA؟
ج: CAPTCHA مناسبة لصفحة الدفع، لكنها لا توضع عادة على زر الإضافة للسلة لأنها تزعج العملاء، كما أن البوتات المتطورة تتجاوزها.
س: هل يحمي Cloudflare من هذا الهجوم؟
ج: يساعد عبر Bot Fight Mode وقواعد WAF، لكن الحماية على مستوى العملية داخل ووردبريس تبقى أدق لأنها تعرف سياق الطلب.
س: هل يؤثر الحارس على كاش الصفحات؟
ج: لا إذا أصدرت الإثبات عبر نقطة REST غير مخزنة مؤقتًا كما في المقال، بدلًا من الاعتماد على تشغيل PHP في كل صفحة.
الخلاصة
لما الهجوم ييجي من آلاف العناوين، متسألش «مين البوت؟»، اسأل «مين يستاهل يعدّل في السلة؟». احمِ العملية نفسها بإثبات موقّع، واشتغل على الهوك الصح، وسيب العدّادات تراقب لو البوت اتعلّم.
مرجع المقال (بالإنجليزية): 8,096 IPs, One WooCommerce Cart Attack: How We Protected the Cart Without Blocking AI Commerce
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.
وإذا كان متجرك على استضافة مشتركة ضعيفة الموارد، فالانتقال إلى باقة بموارد أعلى وسيرفر LiteSpeed يقلل أثر هجوم كهذا كثيرًا، وهذه الاستضافة التي نستخدمها في عرب CMS جرّب Hostinger.



