يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.
قبل كم سنة، كنت شغال على نظام متجر إلكتروني لواحد من العملاء. كان النظام مربوط مع بوابة دفع عالمية، وكل ما تصير عملية دفع ناجحة، بوابة الدفع بتبعتلنا “إشعار” عن طريق ويب هوك (Webhook) عشان نحدّث حالة الطلب في قاعدة البيانات ونبعت إيميل تأكيد للزبون. الأمور كانت ماشية تمام، والنظام شغال زي الساعة.
لحد ما في يوم صحيت الصبح على عشرات الإشعارات من نظام المراقبة تبعنا. السيرفر كان بيعاني، وقاعدة البيانات حجمها تضاعف، وآلاف الإيميلات الوهمية انبعتت لزبائن ما طلبوا إشي أصلاً! بعد شوية بحث وتحليل، اكتشفنا المصيبة: حدا “ذكي” اكتشف الرابط تبع الـ Webhook تبعنا، وبكل بساطة عمل سكربت صغير يضل يبعتلنا طلبات POST وهمية طول الليل. الرابط كان مكشوف، وما عليه أي نوع من الحماية. حرفياً، كان باب خلفي مفتوح على مصراعيه لأي حدا في الشارع يدخل ويعبث بالنظام.
هذاك الموقف علّمني درس قاسي: الـ Webhook أداة جبارة، لكنها سيف ذو حدين. إذا ما أمّنتها صح، بتتحول من نعمة إلى نقمة. ومن يومها، صار تأمين الـ Webhooks هوس عندي، واليوم حاب أشاركم خبرتي في هالموضوع عشان ما حدا يوقع بنفس الغلطة.
شو قصة الـ Webhook أصلاً؟
قبل ما ندخل في تفاصيل التأمين، خلينا نوحّد المفاهيم. تخيل معي السيناريو التالي:
إنت باني تطبيق وبدك تعرف متى المستخدم تبعك غيّر صورة البروفايل تبعته على خدمة ثانية (زي GitHub مثلاً). عندك طريقتين:
- طريقة الـ Polling (السؤال المستمر): إنك تبرمج تطبيقك يضل يسأل سيرفرات GitHub كل دقيقة: “هل المستخدم غيّر صورته؟ هل غيّرها هلأ؟ طيب هلأ؟”. هاي الطريقة مرهقة لسيرفرك ولسيرفراتهم، وبتستهلك موارد على الفاضي.
- طريقة الـ Webhook (الإشعار عند الحدث): إنك تعطي GitHub رابط (URL) خاص فيك، وتحكيلهم: “يا جماعة، بس المستخدم يغير صورته، ابعتولي إشعار على هاد الرابط”. وهيك، بدل ما تضل تسأل، الخدمة نفسها بتخبرك لما الحدث يصير.
الـ Webhook هو ببساطة آلية بتسمح للتطبيقات تتواصل مع بعضها بشكل فوري بناءً على الأحداث (Event-driven). بدل ما تسحب البيانات، البيانات هي اللي “بتدفع” نفسها لتطبيقك. جميل، صح؟ لكن هنا تكمن الخطورة.
ليش الـ Webhook ممكن يكون “باب خلفي”؟
الرابط (URL) تبع الـ Webhook هو عبارة عن واجهة برمجية (API Endpoint) عامة ومكشوفة على الإنترنت. أي شخص بيعرف هاد الرابط بيقدر يبعتله طلبات. إذا ما كنت عامل حسابك، ممكن تتعرض لمشاكل كبيرة.
1. استقبال بيانات مزيفة وغير موثوقة (Data Forgery)
زي ما صار معي بالضبط. أي شخص ممكن يقلّد شكل البيانات اللي بتبعتها الخدمة الأصلية (زي بوابة الدفع)، ويبعتلك طلبات وهمية. هاي الطلبات ممكن تسببلك مشاكل مثل:
- تلويث قاعدة البيانات ببيانات غير صحيحة.
- إجراء عمليات حساسة بالخطأ (مثل شحن منتج لم يتم شراؤه).
- إرسال رسائل سبام للمستخدمين.
2. هجمات حجب الخدمة (Denial-of-Service – DoS)
المهاجم ممكن يغرق الـ Endpoint تبعك بآلاف الطلبات في الثانية. إذا كان الكود تبعك بيعمل عملية معقدة أو طويلة لكل طلب، سيرفرك رح ينهار تحت الضغط ويتوقف عن خدمة المستخدمين الحقيقيين.
3. هجمات تزوير الطلب من جانب الخادم (SSRF – Server-Side Request Forgery)
هاي من أخطر الهجمات. بعض المطورين بيعملوا غلطة فادحة، وهي إنهم بيقرأوا رابط من البيانات اللي جاية مع الـ Webhook (الـ Payload)، وبيخلوا السيرفر تبعهم يفتح هاد الرابط. المهاجم ممكن يستغل هاي الثغرة ويبعتلك رابط لخدمة داخلية على شبكتك الخاصة (مثلاً http://192.168.1.50/admin) واللي المفروض ما تكون مكشوفة للإنترنت. بهي الطريقة، المهاجم بيستخدم سيرفرك كـ “واسطة” عشان يوصل لأماكن حساسة داخل شبكتك.
دليلك العملي لتسكير الباب الخلفي (خطوة بخطوة)
طيب يا أبو عمر، خوفتنا! شو الحل؟ الحل بسيط ومنظم، وبحتاج شوية وعي وتركيز. خلينا نمشي فيهم خطوة خطوة.
الخطوة الأولى: التحقق من توقيع الطلب (Signature Verification) – الأهم على الإطلاق!
هاي هي أهم وأقوى طريقة لتتأكد إنه الطلب اللي وصلك جاي من المصدر الحقيقي ومش من محتال. الفكرة بسيطة:
أغلب الخدمات المحترمة (زي Stripe, GitHub, Shopify) بتسمحلك تعرف “سر” مشترك (Shared Secret) بينك وبينهم. لما يبعتولك Webhook، بيعملوا التالي:
- بيوقعوا محتوى الطلب (الـ Payload) باستخدام هاد السر، وبينتجوا “توقيع” (Signature).
- بيبعتوا هاد التوقيع مع الطلب، غالباً في الـ Header (مثلاً `X-Hub-Signature-256` في GitHub).
دورك إنت كسيرفر مستقبل هو كالتالي:
- لما يوصلك الطلب، بتاخد محتواه الخام (Raw Body).
- بتستخدم نفس السر المشترك عشان تولّد توقيع بنفسك، بنفس الخوارزمية (غالباً HMAC-SHA256).
- بتقارن التوقيع اللي إنت ولّدته مع التوقيع اللي وصلك في الـ Header.
- إذا تطابقوا، مبروك! الطلب أصلي وموثوق. إذا ما تطابقوا، ارمي الطلب فوراً وارجعله خطأ `401 Unauthorized`.
مثال عملي (Node.js و Express)
لنفترض إنك بتستخدم GitHub Webhooks. السر تبعك مخزّن في متغير بيئة اسمه `GITHUB_WEBHOOK_SECRET`.
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
// مهم جداً: لازم نقرأ الـ Body كنص خام عشان التوقيع يزبط
// لهيك بنستخدم bodyParser.raw
app.post('/webhook/github', bodyParser.raw({ type: 'application/json' }), (req, res) => {
// 1. استخراج التوقيع من الهيدر
const signature = req.get('X-Hub-Signature-256');
if (!signature) {
return res.status(401).send('Signature required');
}
// 2. تحضير التوقيع تبعنا
const secret = process.env.GITHUB_WEBHOOK_SECRET;
const computedSignature = `sha256=${crypto
.createHmac('sha256', secret)
.update(req.body) // استخدم الـ Raw Body اللي قرأناه
.digest('hex')}`;
// 3. المقارنة بشكل آمن
// لازم نستخدم crypto.timingSafeEqual عشان نتجنب هجمات الـ Timing Attacks
const isSignatureValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computedSignature)
);
if (!isSignatureValid) {
console.warn('Invalid signature received!');
return res.status(401).send('Invalid signature');
}
// إذا وصلنا لهون، معناها التوقيع صحيح!
console.log('Webhook received and verified successfully!');
// هلأ بنقدر نعمل parse للـ JSON ونكمل شغلنا
const payload = JSON.parse(req.body.toString());
// ... اعمل اللي لازم تعمله بالـ payload ...
res.status(200).send('Received');
});
const PORT = 3000;
app.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
نصيحة من أبو عمر: السر المشترك (Webhook Secret) هو مفتاح بيتك الرقمي. عامله زي كلمة سر حسابك البنكي. خزّنه دائماً في متغيرات البيئة (Environment Variables) أو في خدمة إدارة أسرار (like HashiCorp Vault or AWS Secrets Manager). ممنوع منعاً باتاً تكتبه مباشرة في الكود تبعك!
الخطوة الثانية: استخدم HTTPS دائماً وأبداً
هاي الخطوة مش خيار، هي إجبارية. إذا كان الـ Endpoint تبعك بيشتغل على HTTP العادي بدون تشفير، فكل مجهودك في التحقق من التوقيع بيروح على الفاضي. أي شخص على الشبكة (Man-in-the-middle) بيقدر يتنصت على الطلب، ويسرق المحتوى والتوقيع، ويعيد استخدامه لاحقاً (Replay Attack). استخدام HTTPS (عبر شهادة SSL/TLS) بيضمن إنه القناة بين المرسل والمستقبل مشفرة ومحمية.
الخطوة الثالثة: قائمة بيضاء للـ IP (IP Whitelisting)
كطبقة حماية إضافية، بعض الخدمات بتنشر قائمة بعناوين الـ IP اللي بتبعت منها الـ Webhooks. بتقدر تضبط إعدادات جدار الحماية (Firewall) أو السيرفر تبعك بحيث ما يقبل طلبات إلا من هاي العناوين المحددة.
لكن انتبه: هاي الطريقة لازم تكون طبقة إضافية، مش الطبقة الوحيدة. ليش؟ لأن عناوين الـ IP ممكن تتغير، وإذا ما كنت متابع التحديثات، ممكن فجأة نظامك يوقف استقبال Webhooks سليمة. الاعتماد الأساسي لازم يكون على التحقق من التوقيع.
الخطوة الرابعة: عالج الطلبات بسرعة، وأجّل الشغل الطويل (Asynchronous Processing)
وظيفة الـ Webhook Endpoint الأساسية هي استقبال الطلب، التحقق منه، والرد بسرعة برسالة `200 OK` عشان تخبر المرسل إنه “الطلب وصل وهو سليم”.
أي عملية بتاخد وقت طويل (مثل إرسال إيميل، معالجة ملفات، تحديثات معقدة في قاعدة البيانات) لازم ما تصير بشكل مباشر داخل دالة الـ Endpoint. ليش؟
- لتجنب الـ Timeout: الخدمة اللي بتبعت الـ Webhook بتتوقع رد سريع. إذا تأخرت، ممكن تعتبر إنه الطلب فشل وتحاول تبعته مرة ثانية، وهاد بيسبب مشاكل رح نحكي عنها كمان شوي.
- للحماية من هجمات DoS: إذا كانت كل عملية بتاخد 5 ثواني، فالمهاجم بيقدر يبعتلك كم طلب بسيط عشان يشغل كل العمليات المتاحة على سيرفرك ويمنعه من استقبال أي طلبات جديدة.
الحل؟ استخدم طابور رسائل (Message Queue) مثل RabbitMQ, SQS, Redis Queues, أو حتى طابور بسيط في الذاكرة لو تطبيقك صغير. الفكرة هي:
- الـ Endpoint يستقبل الطلب ويتحقق من التوقيع.
- إذا كان سليم، بيضيف “مهمة” مع بيانات الطلب (Payload) للطابور.
- بيرجع فوراً `200 OK`.
- في الخلفية، عندك “عامل” (Worker) منفصل بيسحب المهام من الطابور وبينفذها وحدة ورا التانية بهدوء.
هيك، الـ Endpoint تبعك بضل سريع وخفيف ومستعد لاستقبال آلاف الطلبات بدون ما يتأثر.
الخطوة الخامسة: تعامل مع إعادة الإرسال بذكاء (Idempotency)
زي ما حكينا، الخدمات ممكن تبعت نفس الـ Webhook أكتر من مرة إذا صار خطأ في الشبكة أو إذا تأخرت في الرد. لو كانت العملية تبعتك هي “خصم مبلغ من حساب”، أكيد ما بدك تخصم المبلغ مرتين!
الحل هو تطبيق مبدأ الـ Idempotency. الكلمة شكلها صعب، بس معناها بسيط: “تنفيذ نفس العملية عدة مرات لازم يعطي نفس نتيجة تنفيذها مرة واحدة”.
كيف نطبقها؟
أغلب الخدمات بتبعت مع كل Webhook معرّف فريد للحدث (Event ID). مثلاً، Stripe بتبعت `id` لكل حدث.
الكود تبعك لازم يعمل التالي:
- قبل ما تنفذ أي عملية، استخرج الـ `event_id` من الطلب.
- ابحث في قاعدة بياناتك (أو في كاش سريع مثل Redis) هل عمرك شفت هاد الـ `event_id` من قبل؟
- إذا كنت شفته: تجاهل الطلب الحالي تماماً ورجع `200 OK`. العملية تمت بالفعل.
- إذا كان جديد: سجّل الـ `event_id` عندك عشان تعرفه المرة الجاية، وبعدين نفّذ العملية.
بهاي الطريقة، حتى لو وصلك نفس الحدث 10 مرات، رح تنفذه مرة واحدة فقط.
الخلاصة: لا تترك الباب مفتوحاً! 🚪
الـ Webhook أداة رائعة بتخلي تطبيقاتنا أكثر تفاعلية وكفاءة، بس زي أي باب على الشارع، لازم نحطله قفل منيح. تجاهل أمن الـ Webhooks مش مجرد إهمال بسيط، هو دعوة مفتوحة للمشاكل اللي ممكن تكلفك بياناتك، وسمعتك، وفلوسك.
للتلخيص، خليك دايماً متذكر هاي النقاط الخمسة:
- ✅ تحقق من التوقيع: هاي هي القاعدة الذهبية. لا تثق بأي طلب بدون توقيع صحيح.
- 🔒 استخدم HTTPS: شفر كل شي. ما في مجال للنقاش.
- ⏱️ استجب بسرعة وعالج لاحقاً: خلي الـ Endpoint تبعك خفيف، وأجّل الشغل الطويل لطابور مهام.
- 🔄 كن مستعداً للإعادة (Idempotency): صمم نظامك عشان يتعامل مع الطلبات المكررة بأمان.
- ➕ أضف طبقات حماية إضافية: مثل الـ IP Whitelisting إذا كانت متاحة.
خليك واعي، واكتب كود آمن. الأمن مش ميزة إضافية، هو أساس أي تطبيق ناجح. والله ولي التوفيق.