Back to all articles

Understanding Application Security: Tokens, Refresh Rotation, & Defense in Depth

A deep dive into system design flaws, JWT security, Access & Refresh Token Rotation, HTTPS limitations, Certificate Pinning, and preventing IDOR attacks.

في يوم من الأيام كنت قاعد مع صاحبي اللي بيدرس Cyber Security بنتكلم في الثغرات اللي بيلاقيها في برامج ومواقع كتير، وساعتها لفت نظري حاجة: معظم الهجمات اللي الهاكرز بيعملوها مش بتعتمد على كسر تشفير أو اختراقات خارقة، بس بتعتمد على استغلال تفاصيل صغيرة في طريقة تصميم الأنظمة ناس كتير بتتغافل عنها.

وده اللي بيمكن أغلب سيناريوهات سرقة الـ Token، وهجمات Man-in-the-Middle، وReplay Attack، وIDOR، وغيرهم كتير.

وأنا بسمع لقيت نفسي بفكر في إزاي المشاريع الحقيقية بتتعامل مع السيناريوهات دي، فروحت أبحث عن الحلول عشان أكون عارف ازاي أحمي مشاريعي الجاية المهمة بالشكل المطلوب.

بس قبل ما نتكلم عن الحلول لازم نفهم يعني إيه Token أصلا.

يعني إيه Token؟

لما بتعمل Login في أي تطبيق أو موقع، أول مرة هي بس اللي بيتبعت فيها الـ Username والـ Password للسيرفر. ولما بيتبعتوا السيرفر بيتأكد إنهم صح، وبعد كده بدل ما يخلي التطبيق يبعت كلمة السر مع كل Request. بيديله Token. تقدر تعتبر الـ Token ده كأنه تصريح دخول مؤقت. من اللحظة دي كل Request التطبيق بيبعته للسيرفر بيكون معاه Token بدل الـ Username والـ Password. وبالتالي كلمة السر مبتتنقلش على الشبكة كل شوية وولا بتتخزن في الـ Local Storage بتاع التطبيق وده في حد ذاته خطوة أأمن.

أشهر نوع من الـ Tokens هو الـ JWT (JSON Web Token) وده بييكون بيحتوي على مجموعة من البيانات اسمها Claims، زي مثلًا الـ User ID، ومعاد الـ Expiration بتاعه، وأي بيانات تانية السيستم يحتاجها. وعلى عكس اللي ممكن تتوقعه فالـ JWT مبيكونش Encrypted، أي حد يقدر يعديه على base64 decoder وهيعرف يقرأ محتواه. بس مش ده كده معناه إن أي حد هيقدر يعمله decode، يعدل البيانات، يعمله encode تاني ويبعت بيانات غلط للسيرفر؟

الحقيقة لا عشان الـ Token اللي طالع من السيرفر بيكون متعلم بحاجة إسمها Signature تسمحلنا ناخد بالنا من أي تلاعب بيحصل فيه. بس يعني ايه Signature أصلا؟ وازاي بنعمل بيه كده؟ عشان نعرف هو ايه المفروض نعرف الأول الطريقة اللي الـ JWT متصمم بيها.

كيفية تصميم JWT Token

الـ Token في JWT بيكون متقسم لـ 3 أجزاء، الجزء الأول بيسموه الـ Header وده بيحتوي على شوية Metadata خاصة بالـ Token، والجزء التاني هو الـ Payload وده بيحتوي على الـ Claims اللي اتكلمنا عليها من شوية اللي بتكون عادة بيانات اليوزر، والجزء التالت عشان نعمله بنجمعله الأول البيانات الموجودة في أول جزئين مع Secret Key احنا عاملينه وده بيكون عبارة عن Random String طويل أوي ومستحيل يتخمن أو يتعمله Brute Force، وكل دول بنعديهم على عملية حسابية معقدة باستخدام Algorithm معين سواء HS256 أو RS256 وبنسجل ناتج العملية دي في الجزء التالت اللي هو الـ Signature نفسه. فبيبقى محتوى الجزء ده بيتحدد على أساس الـ Algorithm اللي مستخدمينه والـ 3 Variables الأساسيين بتوعنا

HS256(header + payload + private key) -> signature

وبيبقى ده شكل الـ Token على بعضه:

header.payload.signature

بعد كده كل جزء بيتعمله base64 encoding ويتبعت الـ Token للتطبيق. ولما التطبيق يبعته تاني للسيرفر بدل الـ Username والـ Password في أي Request بعد كده السيرفر بيعمله decoding وبعدها بياخد الـ Header والـ Payload والـ Secret Key المتسجل عندنا ويعديهم على نفس الـ Algorithm ويشوف هيطلعوا نفس الـ Signature اللي موجود في الـ Token ولا لا.

عشان ناخد مثال على ده، هنفترض إن الهاكر وصل للـ Token بأي شكل من الأشكال وجرب يعمله decoding، فهيعرف يحوله من شكل زي ده:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSIsImFkbWluIjp0cnVlLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

للشكل ده:

{"alg":"HS256", "typ":"JWT"}
{"sub":"1234567890", "name":"John Doe", "email":"[email protected]", "admin":false, "iat":1516239022, "exp":1516242622}
~RR1d)1_ߣΓWB0

السطر الأول هو الـ Metadata زي ما قولنا، والتاني هو الـ Payload، والسطر التالت اللي فيه كلام مش مفهوم ده هو الـ Signature بتاعتنا.

الهاكر لو جرب يعدل الـ Value بتاع admin من false لـ true مثلا وعمل encoding للـ Token تاني وبعته للسيرفر، السيرفر هيعمل نفس العملية الحسابية اللي عملها وهو بيعمل الـ Token تاني

HS256(header + payload (modified) + private key) -> signature

بس هنا هنلاقي إن الـ Signature اللي طلعتنا هتبقى مختلفة عن الـ Signature اللي جاية مع الـ Token، عشان الـ Payload اللي تم استخدامه في حسابه اختلف، فمستحيل يحصل Matching بين ناتجين واحنا مغيرين Variable فيهم عن التاني. في حالة دي الهاكر مستحيل يقدر يعمل Signature سليم لإنه هيحتاج الـ Secret Key بتاعنا عشان من غيره المعادلة هتبقى ناقصة، ولو بعت الـ Token بنفس الـ Signature اللي طلع من السيرفر بيه بس مغير جزء تاني منه كده ناتج المعادلة هيتغير فمش هيحصل Matching زي ما لسه حاصل. فطالما الـ Secret key ده متسربش، الهاكر معندهوش أي طريقة يعدل بيها على الـ Token من غير ما ناخد بالنا.

فبكده عرفنا ازاي نصمم JWT Token سليم نقدر نبعته للتطبيق واحنا متطمنين. بس في حاجة زيادة لسه متكلمناش عنها: في أغلب الأنظمة، السيرفر مش بيطلع Token واحد بس، بيطلع اتنين.

Access Token و Refresh Token

نوع الـ Token الأول اللي ببيطلع من الـ JWT اسمه Access Token، وده اللي التطبيق بيحتاج يبعته مع كل Request عشان يثبت هوية اليوزر، وده عمره بيكون قصير في الغالب كذا دقيقة أو ساعة مثلًا عشان نحاول نخلي الهاكر ميلحقش يعمل بيه حاجة مفيدة حتى لو سرقه.

ونوع الـ Token التاني اسمه Refresh Token، وده عمره أطول بكتير، ممكن يبقى بالأسابيع أو بالشهور. بس التطبيق مش بيبعته مع كل Request، بيبعته بس لما الـ Access Token يحصله Expire بهدف إنه يطلب من السيرفر إنه يطلع Access Token جديد لليوزر من غير ما يضطر يعمل Login كل شوية. بالطريقة دي اليوزر بيفضل مسجل دخوله لأسابيع أو شهور وفي نفس الوقت الـ Access Token نفسه بيفضل عمره قصير.

بس هنا هتظهر مشكلة جديدة، وهي إن لو الـ Refresh Token ده نفسه اتسرق بطريقة ما، الهاكر هيقدر يفضل يطلع في Access Tokens جديدة لنفسه عادي. ففي الحالة دي هنعمل ايه؟

Refresh Token Rotation و Reuse Detection

في تيكنيكس منتشرة بيحلوا المشكلة دي اسمهم Refresh Token Rotation و Reuse Detection.

الفكرة فيهم إن كل Refresh Token يتم استخدامه مرة واحدة بس. وأول ما التطبيق يبعته تاني للسيرفر، السيرفر يديله Refresh Token جديد ويـ Invalidate القديم نهائيًا. بس مش بيمسحه من السيرفر، بيفضل متسجل إن ده كان Refresh Token لليوزر الفولاني بس تم استخدامه مرة خلاص.

تخيل بقى إن الهاكر قدر يسرق نسخة من الـ Refresh Token من اليوزر، فبكده بقى في نسختين من نفس الـ Token، نسخة مع اليوزر الحقيقي ونسخة مع الهاكر.

لو اليوزر استخدم الـ Refresh Token الأول، السيرفر هيطلعله واحد جديد وهيعتبر القديم انتهى ومبقاش صالح للاستخدام خلاص، بعدها بدقايق أو ساعات الهاكر هيحاول يستخدم النسخة اللي سرقها.

بالنسبة للسيرفر المفروض الـ Token ده استخدم قبل كده ومات خلاص، فإزاي ظهر تاني؟ أكبر تفسير منطقي هو إن في حد تاني خد نسخة منه وده معناه إن الـ Refresh Token اتسرب. أغلب الأنظمة ساعتها بتلغي كل Sessions اليوزر وتجبره يعمل Login من الأول. اليوزر الحقيقي هيسجل دخوله تاني من غير مشاكل بالـ Username والـ Password والهاكر هو اللي هيفقد الوصول تمامًا لإن الـ Token اللي معاه مبقاش له لازمة.

ليه HTTPS مش كفاية لوحده؟

لو وصلت لغاية هنا فممكن يجي على بالك سؤال:

“طب ما إحنا أصلا بنستخدم HTTPS فايه لازمة كل ده؟”

الـ HTTPS مهم في إنه بيـ Encrypt الـ Connection بالكامل بين التطبيق والسيرفر، يعني أي بيانات ماشية بينهم سواء Password أو Token أو أي بيانات تانية بتتنقل بشكل مشفر فبالتالي أي حد هيعترض الـ Connection مش هيقدر يفهم محتواه. لكن الـ HTTPS بيحمي البيانات أثناء انتقالها فقط. لو الـ Token اتسرق بعد ما وصل للجهاز أو من Log أو Backup أو Malware موجود على الجهاز أو أو أو فالـ HTTPS خلاص كده ملهوش دعوة هو عمل اللي عليه. فعشان كده حماية الـ Tokens نفسها بتفضل مطلوبة حتى لو الـ Connection كله Encrypted.

وعلى ذكر الـ HTTPS فمن الحاجات اللي أكتشفتها مؤخرًا وعجبتني برضو فكرة الـ Certificate Pinning.

Certificate Pinning

لما التطبيق يتصل بالسيرفر باستخدام HTTPS السيرفر بيبعت حاجة اسمها SSL/TLS Certificate، تقدر تعتبرها بطاقة هوية رقمية للسيرفر بتثبت إن التطبيق بيتكلم مع السيرفر الحقيقي، وكمان دي اللي بتسمح بإنشاء الـ Encrypted Connection بين الطرفين.

في الطبيعي الجهاز بيثق في أي Certificate صادرة من جهة موثوقة Certificate Authority، لكن لو عملت Certificate Pinning التطبيق بيوافق على الـ Certificate الخاصة بسيرفره هو بس وبيرفض أي Certificate تانية حتى لو كانت صادرة من جهة موثوقة. وده بيصعب جدًا تنفيذ هجمات Man-in-the-Middle لأن الهاكر مش هيقدر يقنع التطبيق إنه هو السيرفر الحقيقي بمجرد إن معاه أي Valid Certificate وخلاص.

لكن في المقابل تطبيقه محتاج إدارة كويسة لإن ممكن الـ Certificate بتاعتك تتغير لأي ظرف فمحتاج تقدر تحدث التطبيق في وقتها بالظبط بالـ Certificate الجديدة عشان يفضل قادر يتواصل مع السيرفر.

الوقاية من ثغرات IDOR

أما بالنسبة للـ IDOR فأهم قاعدة هي إن هوية اليوزر لازم تتحدد من البيانات الموجودة جوا الـ Token نفسه خصوصًا الـ User ID، مش أي User ID الـ Client يبعته في الـ Request ناخد بيه. يعني لو اليوزر بعت User ID مختلف في الـ Body أو الـ URL السيرفر المفروض يتجاهله تمامًا ويعتمد على الـ User ID اللي موجود جوا الـ Token عشان ده اللي ضامنين إن مستحيل يحصل تلاعب فيه من غير ما ناخد بالنا بما إن Signed بالـ Key بتاعنا زي ما قولنا في الأول عكس باقية بيانات الـ Request اللي ممكن تتعدل بسهولة.

وبعد ما الهوية تتحدد، السيرفر يقرر صلاحياته بناءً على الـ Role أو الـ Permissions أو ملكية الـ Resource المطلوبة (RBAC).

ولو في طبقة حماية إضافية على الـ Database فلازم نتأكد إنها شغالة فعلاً مش مجرد موجودة في الديزاين وخلاص.

الأمان طبقات

في الآخر أكتر حاجة خرجت بيها من الكلام ده إن الأمان عمره ما كان Feature واحدة.

  • HTTPS بيحل مشكلة.
  • Refresh Token Rotation بيحل مشكلة مختلفة.
  • Certificate Pinning بيعالج سيناريو مختلف.
  • RBAC بيقفل باب هجمات مختلفة.

كل طبقة بتحمي جزء مختلف من النظام، ومفيش طبقة تقدر تعوض غياب الباقي. لو بتشتغل على Backend أو Frontend مسؤول فيه عن الـ Authentication، اسأل نفسك سؤال بسيط:

“لو حد سرق الـ Token بتاع اليوزر دلوقتي، هيقدر يعمل إيه؟”

الإجابة على السؤال ده غالبًا هتوضحلك فين أكبر نقطة ضعف في السيستم عندك.