انتقلت TrendVidStream بكامل حزمة المصادقة الخاصة بها إلى نظام "رموز التحديث الدوارة" (rotating-refresh-token) الذي يكتشف الرمز المسروق في اللحظة التي يُعاد استخدامها فيها. وقد حول هذا التغيير رمز JWT الذي كان صالحًا لمدة 30 يومًا ويسمح للمهاجم بالتجول بحرية، إلى بيانات اعتماد قصيرة الأمد تؤدي فورًا إلى إغلاق الحساب.
أدت عملية اختراق واحدة إلى فرض هذا التحول: حيث قام SDK تابع لشريك بتخزين JWT صالح لمدة 30 يومًا كنص مجرد (plain text)، فقام مهاجم باستخراجه وإعادة استخدامه من بلد آخر، ولم يكن هناك حل سوى تدوير مفتاح التوقيع — وهي عملية أدت إلى تسجيل خروج كل مستخدم. أعاد هذا الحادث تشكيل أمن الرموز في الشركة، وهو ما يدعم الآن خدمة بث الفيديو.
لماذا فشل النموذج القديم
تُعد رموز JWT (JSON Web Tokens) كتلًا موقعة ومكتفية ذاتيًا تتيح للخادم التحقق من الطلب دون الحاجة إلى البحث في قاعدة البيانات. لكن هذه السهولة تخفي تكلفة باهظة: فإذا استمر الرمز صالحًا لأسابيع، فإن سرقته تمنح المهاجم أسابيع من الوصول. في عملية الاختراق، ظل الرمز المسروق صالحًا حتى انتهاء صلاحيته بعد 30 يومًا لأنه لم تكن هناك طريقة لإبطال مفعوله مبكرًا.
يعد تدوير مفتاح التوقيع الطريقة العالمية الوحيدة لإبطال مفعول جميع الرموز، ولكنه يجبر كل مستخدم على تسجيل الدخول مرة أخرى، مما يعطل الخدمة ويقوض الثقة. لم يكن الخلل في رمز JWT نفسه، بل في الاعتماد على بيانات اعتماد واحدة طويلة الأمد.
التصميم الجديد باختصار
تصدر TrendVidStream الآن نوعين من الرموز:
- Access tokens (رموز الوصول) – صالحة لمدة 15 دقيقة؛ وهي تحمل الأذونات اللازمة لكل استدعاء API.
- Refresh tokens (رموز التحديث) – رموز تُستخدم لمرة واحدة لاستبدال رمز وصول قصير الأمد بزوج جديد.
عندما يقدم العميل رمز تحديث، يقوم الخادم بما يلي:
- التحقق من توقيع الرمز والادعاءات (claims) الخاصة به.
- التحقق مما إذا كان الرمز قد استُخدم بالفعل.
- إذا نجح التحقق، يصدر رمز تحديث جديد تمامًا ورمز وصول جديد صالح لمدة 15 دقيقة.
- يحدد رمز التحديث القديم كرمز "مستخدم".
إذا فشلت الخطوة 2 — أي ظهر الرمز نفسه للمرة الثانية — يعامل الخادم ذلك كإشارة سرقة ويلغي مفعول "عائلة" الرموز بأكملها المرتبطة بجلسة تسجيل الدخول تلك. ويُجبر كل من الضحية والمهاجم على تسجيل الدخول مرة أخرى، مما يقطع الطريق على الاختراق.
تتبع عائلات الرموز
بدلاً من التعامل مع كل رمز كسجل معزول، يقوم النظام بتجميعها في عائلات تبدأ عند تسجيل دخول المستخدم. وتنشئ كل عملية تدوير عضوًا جديدًا في تلك العائلة. ويقوم مخطط SQLite المستخدم في الإنتاج بتخزين:
- family_id – معرف ثابت للجلسة بأكملها.
- token_id – معرف فريد لكل رمز تحديث.
- generation – عداد مفيد لتصحيح الأخطاء.
- used_at – الطابع الزمني لوقت استرداد الرمز.
- revoked – علامة (flag) تؤدي عند ضبطها إلى تعطيل العائلة بأكملها.
عند اكتشاف حدث إعادة استخدام، يتم ضبط علامة revoked لـ family_id المعني، مما يؤدي فورًا إلى إبطال مفعول كل رمز ينتمي إلى الجلسة المخترقة. وهذا يحول عملية الاختطاف الصامتة إلى تنبيه عالي الدلالة يظهر في السجلات.
ثلاث قواعد للتنفيذ تحافظ على موثوقية النظام
التحقق من التوقيع أولاً يمكن للمهاجم تخمين معرفات الرموز وتفعيل عمليات إلغاء جماعية إذا قام الخادم بالتحقق من "هل استُخدم من قبل؟" قبل التحقق من التوقيع. إن تأكيد الأصالة أولاً يمنع هجمات حجب الخدمة (DoS) غير الضرورية.
السماح بفترة سماح (grace window) غالبًا ما ترسل تطبيقات الهاتف طلبات تحديث اثنين في تتابع سريع عند انتهاء صلاحية الرمز. إذا كان الخادم صارمًا للغاية، فسيتم تصنيف الطلب الثاني كإعادة استخدام، مما يؤدي إلى تسجيل خروج مستخدم شرعي. توفر بضع ثوانٍ من المهلة إمكانية إرجاع زوج جديد حتى للرمز "المستخدم"، مما يقلل من حدة حالات التسابق (race conditions).
تغليف التدفق بالكامل في عملية (transaction) مع قفل الصفوف بدون خاصية الذرية (atomicity)، قد يعتقد طلبان متزامنان أنهما الأولان في استخدام الرمز، مما يؤدي إلى إصدار رموز تحديث مكررة وكسر ضمان الاستخدام لمرة واحدة. تضمن عملية قاعدة البيانات التي تقفل صف الرمز نجاح طلب واحد فقط.
الخلاصة: من خلال جعل رموز التحديث تُستخدم لمرة واحدة ومراقبة إعادة استخدامها، يمكن للنظام تحويل كل بيان اعتماد مسروق إلى إنذار، مما يحمي المستخدمين دون إجبار الجميع على تسجيل الخروج من المنصة.
