← All posts

آلية القفل لدينا بعد خمس كلمات مرور خاطئة لم تقفل أحداً على الإطلاق

Authagonal·August 5, 2026

قفل الحساب أمر أساسي لا غنى عنه. خمس كلمات مرور خاطئة ويُقفل الحساب لعشر دقائق، وهذا يحوّل تخمين كلمات المرور عبر الإنترنت من تهديد حقيقي إلى أمر لا طائل منه: يحصل المهاجم على خمس محاولات لكل حساب كل عشر دقائق، وهذا لا يساوي شيئاً. كان لدينا ذلك. كانت العتبة خمساً، والنافذة عشر دقائق، والشيفرة التي تفحصها صحيحة، وكانت تعمل بإتقان في كل مرة اختبرناها، لأننا في كل مرة اختبرناها كنا نكتب كلمة المرور الخاطئة خمس مرات متتالية، واحدة تلو الأخرى، مثل إنسان.

المهاجم ليس إنساناً يكتب واحدة تلو الأخرى. المهاجم يرسل خمسمئة دفعة واحدة. وعندما فعلنا ذلك، بالكاد تحرّك العدّاد الذي كان من المفترض أن يتوقف عند خمس، ولم يُقفل الحساب أبداً. كان بإمكانك أن تُمطر حساباً واحداً بعدد غير محدود من التخمينات، وكان الضابط الوحيد المصمَّم لإيقاف هذا بالضبط جالساً بلا فعل، لا لأن المنطق كان خاطئاً بل لأنه افترض أنه لن يُسأل سوى سؤال واحد في كل مرة.

عدّاد يفقد العدّ

فعلت شيفرة تسجيل الدخول الفاشل الأمر البديهي. عند كلمة مرور خاطئة كانت تحمّل المستخدم، وتضيف واحداً إلى عدد محاولاته الفاشلة، وتفحص ما إذا كانت القيمة الجديدة قد بلغت خمساً، ثم تحفظ المستخدم من جديد. قراءة، تعديل، كتابة. مكتوبةً كما يفكّر بها الإنسان، هي صحيحة.

مكتوبةً كطلبين يصلان في اللحظة نفسها، هي ليست كذلك. كلاهما يقرأ العدّاد، وكلاهما يقرأ القيمة نفسها، لنقل ثلاثة. كلاهما يضيف واحداً فيحصل على أربعة. كلاهما يكتب أربعة من جديد. حدثت محاولتان فاشلتان، لكن العدّاد انتقل من ثلاثة إلى أربعة. تبخّرت إحدى الزيادتين ببساطة، دهستها الأخرى، لأن كليهما كان يعمل انطلاقاً من النقطة القديمة نفسها، وحطّت الكتابة الثانية فوق الأولى كأن الأولى لم تحدث قط.

الآن أطلق خمسمئة كلمة مرور خاطئة دفعة واحدة. تتكدّس بعضها فوق بعض بهذه الطريقة تماماً، كل واحدة تقرأ عدّاداً منخفضاً وتكتب عدّاداً أقل انخفاضاً بقليل، والزيادات تتبخّر بالمئات. يرتفع العدّاد قليلاً ويتوقف في مكان بعيد كل البعد عن خمسة. لا تُتجاوز عتبة القفل أبداً، لا لأن الفحص خاطئ، فهو يُفحص بأمانة في كل مرة، بل لأن الرقم الذي يفحصه كذبة ركّبتها حشدٌ من الطلبات كلها تدهس بعضها بعضاً. حماية القوة الغاشمة هُزمت لا بخلل منطقي بل بالتزامن، وهو نوع الخلل الذي يجتاز كل اختبار تسلسلي ستكتبه في حياتك ويفشل لحظة يلتقي بمهاجم حقيقي، وهو الطرف الوحيد الذي لديه دافع لإرسال الطلبات بالتوازي.

موضع سكن هذا الخلل له أهمية. لم يكن مدفوناً في طبقة تخزين ما. كان هناك تماماً في معالج تسجيل الدخول، على مرأى من الجميع، ++ وحفظ، أكثر شيفرة طبيعية في العالم. لا شيء في قراءتها يوحي بالخطر. عليك أن تكون مفكّراً في تشغيل نسختين منها في آن واحد كي يظهر الخلل، وقراءة الشيفرة سطراً سطراً هي بالضبط الحالة الذهنية التي لا تكون فيها كذلك.

جعل العدّاد ذرّياً على مخزن لا يستطيع العدّ

على الإصلاح أن يجعل عملية القراءة والتعديل والكتابة كلها غير قابلة للتجزئة: لا يجوز لأي محاولة أخرى أن تتسلل بين قراءتنا وكتابتنا. الجزء المثير للاهتمام أننا على مخزن مفتاح وقيمة لا يملك عملية زيادة. لا يمكنك أن تقول له "أضف واحداً إلى هذا الحقل"، وهي العملية الأولية التي كانت ستمنحك إياها قاعدة بيانات ذات عدّادات ذرّية. لا يمكنك سوى قراءة صف وكتابة صف.

ما يمنحك إياه المخزن هو الكتابة الشرطية. كل صف يحمل وسم إصدار يتغيّر مع كل كتابة، ويمكنك أن تقول: أعِد كتابة هذا الصف، لكن فقط إذا كان إصداره ما زال هو الذي قرأته. إذا كتب شخص آخر في هذه الأثناء، تحرّك الإصدار، ورُفضت كتابتك بدلاً من أن تدهس كتابته بصمت.

هذا يحوّل سباق الزيادة المفقودة إلى تصادم مضبوط. اقرأ الصف وإصداره. أضف واحداً. حاول أن تعيد الكتابة مشروطةً بذلك الإصدار. إذا نجحت، فإن زيادتك حطّت قطعاً فوق القيمة التي قرأتها، دون أن يُفقد شيء. إذا رُفضت، فهذا يعني أن محاولة أخرى سبقتك، فتعيد قراءة العدّاد الأعلى الآن وتحاول من جديد. كرّر عند الرفض، عدداً محدوداً من المرات، فتُجبَر كل زيادة على أن تصطف خلف سابقتها بدلاً من دهسها. يعود العدّ صادقاً من جديد، وهجوم الخمسمئة دفعة واحدة يمشي الآن بالعدّاد مباشرةً إلى خمسة ويُطلق القفل، وهذا هو المقصد كله.

حملنا الشكل نفسه إلى خلفية التخزين الأخرى لدينا، التي لا تملك بدورها زيادة لكنها تقدّم أيضاً كتابة شرطية، هذه المرة مشروطة بقيمة العدّاد نفسها بدلاً من وسم الإصدار: اكتب العدّاد الجديد فقط إذا كان العدّاد المخزَّن ما زال هو ما قرأته. الضمان نفسه، معبَّراً عنه بلغة ذلك المخزن. يتجاوز الدرس أي قاعدة بيانات بعينها: حين تعوزك الزيادة الذرّية، تعيد الكتابة الشرطية مع حلقة إعادة محاولة بناءها، لأن الشرط هو ما يرفض الدهس القديم الذي كان يفقد عدّك.

يستحق تباين متعمَّد واحد الإشارة. جعلنا مسار الفشل ذرّياً وتركنا مسار النجاح، الذي يعيد ضبط العدّاد إلى صفر عند تسجيل دخول صحيح، ككتابة عادية يفوز فيها آخر كاتب. التزامن لا يهدّد إعادة الضبط: عدة عمليات تسجيل دخول ناجحة تتسابق كلها تريد كتابة صفر، ولا يهم أيها يفوز. الزيادة وحدها عدائية، لأن الزيادة وحدها هي الشيء الذي يحاول المهاجم كبته. تنفق كلفة الكتابات الشرطية وإعادة المحاولات حيث يضغط الخصم، لا حيث لا يضغط.

افترض أن أحدهم يعدّ

القيمة الحقيقية لهذا الخلل كانت الحالة الذهنية التي فرضها، لأننا في اليوم نفسه الذي أصلحناه فيه ذهبنا نبحث عن كل شيء آخر افترض بهدوء طلباً واحداً في كل مرة أو افترض أن لا أحد يقيس، فوجدنا مجموعة صغيرة من الأشقّاء.

كانت ردود تسجيل الدخول لدينا تخبر المهاجمين بالحسابات الموجودة. حساب غير موجود، وآخر معطَّل، وآخر مقفل، وكلمة مرور خاطئة، كل منها ينتج جواباً مميَّزاً، بل إن مسار "لا مستخدم كهذا" كان يعود أسرع لأنه يتخطى تجزئة كلمة المرور كلياً، فكان بإمكان المهاجم أن يعرف من يملك حساباً بمراقبة الحالة أو الساعة. جمعنا الأجوبة في جواب واحد: مستخدم مجهول وكلمة مرور خاطئة يعيدان الآن الرد نفسه بالضبط، والمستخدم المجهول لا يزال يُتحقق منه مقابل تجزئة وهمية ثابتة كي يكون التوقيت نفسه سواء أوُجد الحساب أم لا. الحساب موجود أو غير موجود، ولا يمكنك أن تعرف من الخارج.

كان في التسجيل التسريب نفسه بصيغة أصعب، لأنك لا تستطيع ببساطة أن تقول دائماً "نجح" حين يحاول أحد تسجيل بريد مأخوذ سلفاً، إذ إن هذا يبدو وكأنه يسلّمه الحساب. لذا فإن تسجيلاً لعنوان مسجَّل سلفاً يعيد الآن النجاح المحايد نفسه تماماً الذي يعيده تسجيل جديد، وينفق زمن تجزئة كلمة المرور نفسه، ولا ينشئ شيئاً، وبدلاً من ذلك يرسل إلى المالك الحقيقي بريداً يقول إن أحدهم حاول تسجيل عنوانه. المهاجم لا يتعلّم شيئاً. والمالك الفعلي يُبلَّغ بهدوء.

وكانت استمارة إعادة تعيين كلمة المرور مدفعَ بريد: لا حدّ لكل عنوان، فكان بإمكانك أن تجعلنا نطلق رسائل إعادة تعيين إلى أي عنوان مراراً، هجوم يستهدف طرفاً ثالثاً ويستهدف سمعة إرسالنا نفسها. صارت الآن تحدّ من المعدّل لكل بريد مستهدَف، وفوق السقف تتخطى الإرسال مع إعادة رد النجاح نفسه كالعادة، كي لا يصير الخنق نفسه طريقة جديدة لاستكشاف العناوين الموجودة.

كل واحد من هذه هو درس خلل القفل يرتدي ثياباً مختلفة. صمّم كما لو أن مهاجماً يرسل الطلبات بالتوازي، ويقيس توقيت ردودك، ويعدّ. لأن الشيء الوحيد الذي يمكنك أن تكون متأكداً منه هو أن من يهاجم تسجيل دخولك لا يفعل ذلك بطلب واحد مهذّب في كل مرة.

لماذا يجب اختبار عدّاد أمني بشكل متزامن

خلل التزامن في ضابط أمني أسوأ من خلل التزامن في أي مكان آخر، لسبب محدد: الطرف صاحب أقوى دافع لإطلاقه هو المهاجم، والظروف التي تطلقه، أي طلبات متزامنة كثيرة، هي بالضبط الظروف التي يخلقها الهجوم ونادراً ما يخلقها الاختبار. لذا فإن قفلاً، أو حدّ معدّل، أو حصة، أو أي ضابط مبني على عدّاد، لا يمكن التحقق منه بزيادته في حلقة ومراقبة انطلاقه. يجب ضربه بشكل متزامن، لأن التزامن هو البيئة الأصلية للمهاجم، والنسخة التسلسلية من الاختبار لا تثبت سوى أن ضابطك يعمل ضد خصم وافق على أن يكون مهذّباً.

وحين تصلّب العدّاد فعلاً، فإن الأداة على مخزن بلا زيادات ذرّية هي الكتابة الشرطية: اقرأ الإصدار، اكتب فقط إذا لم يتحرّك، أعد المحاولة إذا تحرّك. تعيد بناء الزيادة الذرّية التي لم تمنحك إياها قاعدة البيانات، من الضمان الوحيد الذي منحك إياه.

إذا كنت تفضّل أن تعمل على مزوّد هوية يفترض سلفاً أن المهاجم يعدّ بالتوازي، فإن Authagonal بنى هذه الضوابط لتُضرب كلها دفعة واحدة، لأن تلك هي الطريقة الوحيدة التي كانت ستُضرب بها فعلاً.