كل بضعة أشهر، يبدو أن إحدى الشركات تتعرض لحادث أمني حيث يُعرض على المستخدمين حساب مستخدم آخر عند تسجيل الدخول. ويُفترض أن العديد من هذه الحوادث ناتجة عن التكوينات الخاطئة للتخزين المؤقت على الويب. في حين أن البعض قد يكون من السهل فهمه من قبل مهندسي البرمجيات بعد فوات الأوان، إلا أن البعض الآخر كان سببه طلب الانهيار، وهي إحدى ميزات خدمات التخزين المؤقت التي يمكن أن تؤدي إلى سلوك غير متوقع. حتى عند استخدام رأس HTTP Cache-Control: no-cache، لا يزال من الممكن حدوث هذا النوع من التخزين المؤقت، مما قد يؤدي إلى إرجاع البيانات الحساسة المخصصة لمستخدم واحد إلى عدة مستخدمين آخرين.
في هذه التدوينة سوف أستكشف هذه المشكلة وأشرح كيفية تجنبها.
التخزين المؤقت على الويب هو أسلوب تحسين لتجنب نقل نفس البيانات عدة مرات، وغالبًا ما يكون ذلك لتقليل زمن الوصول، أو لتجنب إنشاء نفس الاستجابة للطلبات المكافئة عدة مرات. قد يعني هذا الاحتفاظ بنسخة من ملف في خادم بالقرب من مدينة رئيسية بحيث لا تضطر الطلبات الواردة من تلك المدينة إلى السفر إلى قارة أخرى، أو قد يعني الاحتفاظ بنسخة من الرد على طلب يتطلب الكثير من العمل للتحضير.
على سبيل المثال، قد تتم استضافة الصفحة الرئيسية لخادم ويب خارج منطقة AWS واحدة، مثل us-east-1، ولكن قد يتم استخدام Amazon CloudFront لتخزين أجزاء من تلك الصفحة الرئيسية مؤقتًا في مواقعها التي يزيد عددها عن 600 موقع. تعمل هذه الفكرة بشكل رائع عندما يصل طلب لـ /images/logo.png حيث يجب أن يرى جميع المستخدمين نفس الشيء، ولكن إذا جاء طلب لـ /images/logo.png /api/user_account_details يجب أن يكون لهذا استجابة مختلفة لكل مستخدم لذلك لا ينبغي تخزينه مؤقتًا.
لقد قمت بتتبع الأحداث الأمنية التي تتطابق مع أعراض مشكلة التخزين المؤقت على الويب (وصلة)، حيث تم تسجيل 14 حادثة، منها 11 حدثت خلال السنوات الأربع الماضية. الدليل الذي أستخدمه في كثير من الأحيان هو مجرد أشخاص ينشرون على Reddit أنه عندما يقومون بتسجيل الدخول إلى حساباتهم، فإنهم يرون ما يشبه حساب شخص آخر، لذلك لا يمكنني التحقق على وجه اليقين من أن هذه الحوادث ناتجة عن التخزين المؤقت على الويب. ومع ذلك، في بعض الأحيان تعترف الشركات بالحادثة، وفي بعض الأحيان تقوم بنشر تقارير بعد الوفاة تحدد السبب المحدد. ذكر اثنان من تلك الحوادث على وجه التحديد السلوك غير المتوقع المتمثل في انهيار الطلب باعتباره السبب الجذري (1, 2).
تحدد سياسات ذاكرة التخزين المؤقت المحتوى الذي سيتم تخزينه مؤقتًا. يستخدمون مفتاح ذاكرة التخزين المؤقت لتحديد وقت إرجاع نفس الاستجابة. بشكل افتراضي، بالنسبة إلى CloudFront، يكون مفتاح ذاكرة التخزين المؤقت هو اسم المجال ومسار URL. تحدد سياسة ذاكرة التخزين المؤقت بعد ذلك مفاتيح ذاكرة التخزين المؤقت التي يجب أن تؤدي إلى التخزين المؤقت. في مثالنا السابق، طلبات /images/logo.png يجب أن تكون مخزنة مؤقتا، في حين /api/* لا ينبغي.
من الصعب اختبار التكوينات الخاطئة للتخزين المؤقت على الويب لأنك تحتاج إلى استخدام عدة مستخدمين ومعرفة المحتوى الذي يجب أن يختلف. ويجب أيضًا مراعاة التوقيت بين الطلبات. يصعب التحقق من سياسات ذاكرة التخزين المؤقت برمجياً لأن سياسة ذاكرة التخزين المؤقت الصحيحة لتطبيق واحد من المحتمل أن تكون خاطئة لتطبيق آخر.
فيما يلي مثال بسيط لكيفية عمل ذاكرة التخزين المؤقت، حيث ينتج عن الطلب الأول نسخة من الاستجابة المخزنة مؤقتًا، بحيث لا يحتاج الطلب الثاني إلى الانتقال إلى الأصل (خادم الويب).
في الرسم البياني أعلاه، إذا كان هناك طلب ل /api/user_account_details تم تخزينها مؤقتًا وإعادتها إلى عدة مستخدمين، فستواجه حادثًا أمنيًا، لأنه سيتم إرسال البيانات الحساسة للمستخدم الأول إلى المستخدم الثاني. لتجنب حدوث ذلك، ستستبعد سياسة ذاكرة التخزين المؤقت أنماطًا معينة من مفاتيح ذاكرة التخزين المؤقت من التخزين المؤقت.
في كثير من الأحيان، سيتضمن خادم الويب الأصلي رؤوسًا على الاستجابات التي تحدد كيفية تخزينها مؤقتًا، حتى يتمكن المطورون من الحفاظ على التعليمات البرمجية وتوجيهات التخزين المؤقت في مكان واحد. لذلك قد يوفر الأصل استجابة تخبر خادم ذاكرة التخزين المؤقت Cache-Control: no-cache أو توجيهات مماثلة. سوف يتبع CloudFront هذا التوجيه ولن يقوم بتخزين الملف مؤقتًا، ولكن في حالة انهيار الطلب، سيتم تجاهل الرأس.
يحدث انهيار الطلب، والذي يشار إليه أحيانًا باسم “دمج الطلب”، عندما يحدث أكثر من طلب لنفس مفتاح التخزين المؤقت قبل إرجاع الاستجابة من الطلب الأول. يتعرف خادم ذاكرة التخزين المؤقت على أن الطلبات المتطابقة المتعددة يمكن أن تطغى على الأصل، لذلك ينتظر الاستجابة من الطلب الأول بدلاً من إرسال جميع الطلبات فور وصولها. وبعد أن يأتي الرد، يتم إرساله إلى جميع الطالبين.
يتماشى كل هذا مع توقعات الكثيرين ويتم إجراؤه لمنع مشاكل القطيع المدوية التي يمكن أن تطغى على الأصل، وهو ما يعد إلى حد كبير فائدة استخدام خادم ذاكرة التخزين المؤقت في المقام الأول. ومع ذلك، يصبح الأمر غير متوقع هو كيفية تعامل Amazon CloudFront مع سياسة التخزين المؤقت في ظل هذه الظروف. يتوقع معظمهم أنه إذا كان الرد يحتوي على Cache-Control: no-cache، إذن يجب أن يتلقى مقدم الطلب الأصلي فقط تلك الاستجابة ويجب أن تنتقل الطلبات المتأخرة الآن إلى الأصل، ولكن بدلاً من ذلك ستتلقى الطلبات المتأخرة نفس الاستجابة التي لم يكن من المفترض تخزينها مؤقتًا! تم توضيح ذلك في وثائق AWS هنا.
لتجنب انهيار الطلب عند الطلبات التي لا ينبغي تخزينها مؤقتًا على Amazon CloudFront، لديك الخيارات التالية:
-
استخدم سياسة ذاكرة التخزين المؤقت المُدارة “CachingDisabled” والتي كما يوحي اسمها ستتجنب كل التخزين المؤقت لنمط مفتاح ذاكرة التخزين المؤقت المحدد.
-
قم بتعيين الحد الأدنى لـ TTL لسلوك ذاكرة التخزين المؤقت على 0 وقم بتكوين الأصل لإرسال رأس HTTP مثل
Cache-Control: no-cacheلكل كائن لا ينبغي تخزينه مؤقتًا. يجب عليك أن تفعل كلا الأمرين. إذا كنت تستخدم ببساطة توجيه رأس HTTP، فستظهر سياسة التخزين المؤقت الخاصة بك تعمل بشكل صحيح للعديد من الاختبارات المتعلقة بالتخزين المؤقت، حتى تقوم بتقديم طلبات متزامنة تؤدي إلى انهيار الطلب. هذا هو الجزء الأكثر أهمية في منشور المدونة الذي يجب ملاحظته، لأنه غير متوقع للكثيرين.
عند اختبار ذاكرة التخزين المؤقت الخاصة بك، لا ينبغي عليك فقط تقديم طلبات من مستخدمين مختلفين في اختبارات تسلسلية، ولكن أيضًا في وقت واحد. تتشابك ذاكرة التخزين المؤقت والخادم الأصلي بشكل كبير، لذا قد تؤدي التغييرات التي يتم إجراؤها على أي منهما إلى عمل التخزين المؤقت بشكل غير صحيح، مما قد يؤدي إلى وقوع حادث أمني. على سبيل المثال، إذا منعت سياسة التخزين المؤقت التخزين المؤقت /api/لكن الأصل قام بتغيير واجهات برمجة التطبيقات لاستخدام المسار /apiv2/ ومن ثم قد يتم تخزين البيانات الحساسة مؤقتًا، وقد يحدث حادث أمني. للحصول على أفضل الممارسات الإضافية حول استخدام CloudFront، راجع AWS الوثائق.
