اكتشف فريق CertiK's Skyfall مؤخرًا نقاط ضعف متعددة في عقد RPC القائمة على Rust في العديد من سلاسل الكتل بما في ذلك Aptos و StarCoin و Sui. نظرًا لأن عقد RPC هي مكونات أساسية للبنية التحتية تربط dApps و blockchain الأساسي ، فإن قوتها أمر بالغ الأهمية للتشغيل السلس. يعرف مصممو Blockchain أهمية خدمات RPC المستقرة ، لذا فهم يعتمدون لغات آمنة للذاكرة مثل Rust لتجنب نقاط الضعف الشائعة التي يمكن أن تدمر عقد RPC.
يساعد اعتماد لغة آمنة للذاكرة مثل Rust عقد RPC على تجنب العديد من الهجمات بناءً على نقاط الضعف في تلف الذاكرة. ومع ذلك ، من خلال مراجعة حديثة ، وجدنا أنه حتى تطبيقات Rust الآمنة للذاكرة ، إذا لم يتم تصميمها وفحصها بعناية ، يمكن أن تكون عرضة لتهديدات أمنية معينة يمكن أن تعطل توفر خدمات RPC.
في هذه المقالة ، سوف نقدم اكتشافنا لسلسلة من نقاط الضعف من خلال الحالات العملية.
** دور عقدة Blockchain RPC **
تعد خدمة استدعاء الإجراء عن بُعد (RPC) الخاصة بـ blockchain هي مكون البنية التحتية الأساسي لـ blockchain من الطبقة الأولى. يوفر للمستخدمين واجهة أمامية مهمة لواجهة برمجة التطبيقات ويعمل كبوابة لشبكة blockchain الخلفية. ومع ذلك ، تختلف خدمة blockchain RPC عن خدمة RPC التقليدية من حيث أنها تسهل تفاعل المستخدم دون المصادقة. يعد التوافر المستمر للخدمة أمرًا بالغ الأهمية ، وأي انقطاع في الخدمة يمكن أن يؤثر بشدة على توافر blockchain الأساسي.
** منظور المراجعة: خادم RPC التقليدي VS blockchain RPC server **
يركز تدقيق خوادم RPC التقليدية بشكل أساسي على التحقق من المدخلات ، والتفويض / المصادقة ، وتزوير الطلب عبر الموقع / تزوير الطلب من جانب الخادم (CSRF / SSRF) ، ونقاط ضعف الحقن (مثل حقن SQL ، وإدخال الأوامر) ، وتسرب المعلومات.
ومع ذلك ، يختلف الوضع بالنسبة لخوادم blockchain RPC. طالما تم توقيع المعاملة ، فلا داعي لمصادقة العميل الطالب في طبقة RPC. باعتبارها الواجهة الأمامية لـ blockchain ، فإن أحد الأهداف الرئيسية لخدمة RPC هو ضمان توفرها. إذا فشلت ، لا يمكن للمستخدمين التفاعل مع blockchain ، ومنعهم من الاستعلام عن البيانات على السلسلة ، أو إرسال المعاملات ، أو إصدار وظائف العقد.
لذلك ، فإن الجانب الأكثر ضعفًا في خادم blockchain RPC هو "التوفر". إذا تعطل الخادم ، يفقد المستخدمون القدرة على التفاعل مع blockchain. والأخطر من ذلك أن بعض الهجمات ستنتشر على السلسلة ، وتؤثر على عدد كبير من العقد ، بل وتؤدي إلى شلل الشبكة بأكملها.
** لماذا ستستخدم blockchain الجديدة RPC الآمنة للذاكرة **
تستخدم بعض سلاسل الكتل ذات الطبقة الأولى المعروفة ، مثل Aptos و Sui ، لغة البرمجة الآمنة للذاكرة Rust لتنفيذ خدمات RPC الخاصة بهم. بفضل الأمان القوي والفحوصات الصارمة لوقت الترجمة ، يجعل Rust البرامج محصنة فعليًا من نقاط الضعف في تلف الذاكرة ، مثل فيض المكدس ، وإحالة المؤشر الفارغ ونقاط الضعف التي لا تحتوي على مرجع لاحق.
لزيادة تأمين قاعدة التعليمات البرمجية ، يتبع المطورون بصرامة أفضل الممارسات ، مثل عدم إدخال تعليمات برمجية غير آمنة. استخدم #! [forbid (unsafe \ _code)] في التعليمات البرمجية المصدر للتأكد من حظر الشفرة غير الآمنة وتصفيتها.
أمثلة على مطوري blockchain الذين ينفذون ممارسات برمجة Rust
لمنع فائض الأعداد الصحيحة ، يستخدم المطورون عادةً وظائف مثل فحص \ _ إضافة ، فحص \ _ فرعي ، تشبع \ _ إضافة ، تشبع \ _ فرعي ، إلخ. بدلاً من الجمع والطرح البسيط (+ ، -). التخفيف من استنفاد الموارد عن طريق تعيين المهلات المناسبة ، وحدود حجم الطلب ، وحدود الطلب على العناصر.
** تهديدات أمان الذاكرة RPC في الطبقة 1 Blockchain **
على الرغم من أنها ليست عرضة لانعدام أمان الذاكرة بالمعنى التقليدي ، إلا أن عُقد RPC تتعرض لمدخلات يتم التلاعب بها بسهولة من قبل المهاجمين. في تطبيق RPC الآمن للذاكرة ، هناك العديد من المواقف التي يمكن أن تؤدي إلى رفض الخدمة. على سبيل المثال ، قد يستنفد تضخيم الذاكرة ذاكرة الخدمة ، بينما قد تؤدي المشكلات المنطقية إلى حدوث حلقات لا نهائية. بالإضافة إلى ذلك ، يمكن أن تشكل ظروف السباق تهديدًا حيث يمكن أن يكون للعمليات المتزامنة تسلسل غير متوقع من الأحداث ، مما يترك النظام في حالة غير محددة. بالإضافة إلى ذلك ، يمكن أن تؤدي التبعيات التي تتم إدارتها بشكل غير صحيح ومكتبات الجهات الخارجية إلى وجود ثغرات أمنية غير معروفة في النظام.
في هذا المنشور ، هدفنا هو لفت الانتباه إلى طرق أكثر إلحاحًا يمكن من خلالها تشغيل حماية وقت تشغيل Rust ، مما يتسبب في إجهاض الخدمات نفسها.
** الذعر الصريح من الصدأ: طريقة لإنهاء خدمات RPC مباشرةً **
يمكن للمطورين إدخال رمز صريح للذعر ، عن قصد أو عن غير قصد. تستخدم هذه الرموز بشكل أساسي للتعامل مع الظروف غير المتوقعة أو الاستثنائية. تتضمن بعض الأمثلة الشائعة ما يلي:
تأكيد! (): استخدم هذا الماكرو عندما يجب استيفاء شرط. إذا فشل الشرط المؤكد ، سينزعج البرنامج ، مما يشير إلى وجود خطأ فادح في الكود.
panic! (): يتم استدعاء هذه الوظيفة عندما يواجه البرنامج خطأ لا يمكنه التعافي منه ولا يمكنه المتابعة.
غير قابل للوصول! (): استخدم هذا الماكرو عندما لا ينبغي تنفيذ جزء من التعليمات البرمجية. إذا تم استدعاء هذا الماكرو ، فإنه يشير إلى خطأ منطقي خطير.
unimplemented! () and todo! (): وحدات الماكرو هذه عبارة عن عناصر نائبة لوظائف غير مطبقة. إذا تم الوصول إلى هذه القيمة ، فسوف يتعطل البرنامج.
undrap (): تُستخدم هذه الطريقة لأنواع الخيار أو النتائج. عند مصادفة متغير Err أو لا شيء ، سيتعطل البرنامج.
** الثغرة 1: تفعيل التأكيد في أداة التحقق من الحركة! **
يستخدم blockchain Aptos مدقق Move bytecode لإجراء تحليل أمان مرجعي من خلال تفسير مجرد لرمز بايت. تعد وظيفة ute () جزءًا من تنفيذ سمة TransferFunctions وتحاكي تنفيذ تعليمات الرمز الثانوي في الكتل الأساسية.
تتمثل مهمة الوظيفة ute \ _inner () في تفسير تعليمات الرمز الثانوي الحالية وتحديث الحالة وفقًا لذلك. إذا قمنا بتنفيذ التعليمات الأخيرة في الكتلة الأساسية ، كما هو موضح بواسطة index == last \ _index ، ستستدعي الوظيفة تأكيد! (self.stack.is \ _empty ()) للتأكد من أن المكدس فارغ. القصد من هذا السلوك هو ضمان أن جميع العمليات متوازنة ، مما يعني أيضًا أن كل دفعة لها فرقعة مقابلة.
في التدفق الطبيعي للتنفيذ ، تكون المكدس دائمًا متوازنة أثناء التفسير المجرد. يتم ضمان ذلك بواسطة Stack Balance Checker ، الذي يتحقق من الرمز الثانوي قبل تفسيره. ومع ذلك ، بمجرد أن نوسع منظورنا إلى مجال المترجمين الفوريين ، نرى أن افتراض توازن المكدس ليس صحيحًا دائمًا.
تصحيح الثغرة الأمنية في دالة التحليل \ _ في AbstractInterpreter
في جوهره ، يحاكي المترجم المجرد الرمز الثانوي على مستوى الكتلة الأساسية. في التطبيق الأصلي ، فإن مواجهة خطأ أثناء ute \ _block ستدفع عملية التحليل لتسجيل الخطأ ومتابعة التنفيذ إلى الكتلة التالية في الرسم البياني لتدفق التحكم. يمكن أن يؤدي هذا إلى إنشاء موقف حيث يمكن أن يتسبب خطأ في كتلة التنفيذ في أن يصبح المكدس غير متوازن. إذا استمر التنفيذ في هذه الحالة ، فسيتم إجراء فحص تأكيد إذا لم يكن المكدس فارغًا ، مما يتسبب في حالة من الذعر.
هذا يعطي المهاجمين فرصة للاستغلال. يمكن للمهاجم تشغيل خطأ من خلال تصميم رمز بايت محدد في ute \ _block () ، ومن ثم قد يقوم ute () بتنفيذ تأكيد إذا لم يكن المكدس فارغًا ، مما يتسبب في فشل التحقق من التأكيد. سيؤدي هذا إلى مزيد من الذعر وإنهاء خدمة RPC ، مما يؤثر على توفرها.
لمنع حدوث ذلك ، يضمن الإصلاح المنفذ إيقاف عملية التحليل بأكملها عندما تواجه وظيفة الحظر / ute خطأ لأول مرة ، وبالتالي تجنب مخاطر الأعطال اللاحقة التي قد تحدث عند استمرار التحليل بسبب عدم توازن المكدس بسبب الأخطاء. يزيل هذا التعديل الظروف التي قد تسبب الذعر ويساعد على تحسين متانة وسلامة المترجم المجرد.
** الضعف 2: إثارة الذعر في StarCoin! **
لدى Starcoin blockchain شوكة خاصة بها لتطبيق Move. في هذا Move repo ، هناك حالة من الذعر في مُنشئ نوع الهيكل! إذا كان تعريف StructDefinition المقدم يحتوي على معلومات الحقل الأصلي ، فسيتم إثارة الذعر بشكل واضح! .
هلع صريح للهياكل المهيأة في إجراءات التطبيع
توجد هذه المخاطر المحتملة في عملية إعادة توزيع الوحدات. إذا كانت الوحدة المنشورة موجودة بالفعل في مخزن البيانات ، فإن تسوية الوحدة النمطية مطلوبة لكل من الوحدة النمطية الحالية ووحدة الإدخال التي يتحكم فيها المهاجم. أثناء هذه العملية ، تقوم وظيفة "normalized :: Module :: new" ببناء هيكل الوحدة النمطية من وحدات الإدخال التي يتحكم فيها المهاجم ، مما يؤدي إلى حدوث "حالة من الذعر!".
المتطلبات الأساسية لروتين التطبيع
يمكن إثارة هذا الذعر من خلال إرسال حمولة مصممة خصيصًا من العميل. لذلك ، يمكن للجهات الضارة تعطيل توفر خدمات RPC.
التصحيح الذعر التهيئة
يقدم تصحيح Starcoin سلوكًا جديدًا للتعامل مع الحالة الأصلية. الآن ، بدلاً من الذعر ، تُرجع قيمة ec فارغة. هذا يقلل من احتمال قيام المستخدمين بإرسال البيانات التي تسبب الذعر.
** الذعر الضمني من الصدأ: طريقة يسهل التغاضي عنها لإنهاء خدمات RPC **
يمكن التعرف بسهولة على حالات الذعر الصريح في التعليمات البرمجية المصدر ، بينما من المرجح أن يتجاهل المطورون حالات الذعر الضمني. عادةً ما تحدث حالات الذعر الضمني عند استخدام واجهات برمجة التطبيقات التي توفرها مكتبات قياسية أو مكتبات تابعة لجهات خارجية. يحتاج المطورون إلى قراءة وثائق API وفهمها تمامًا ، وإلا فقد تتوقف برامج Rust الخاصة بهم بشكل غير متوقع.
ذعر ضمني في BTreeMap
لنأخذ BTreeMap من Rust STD كمثال. BTreeMap عبارة عن هيكل بيانات شائع الاستخدام ينظم أزواج المفتاح والقيمة في شجرة ثنائية مرتبة. يوفر BTreeMap طريقتين لاسترداد القيم بالمفتاح: get (& self، key: & Q) والفهرس (& self، key: & Q).
يسترد التابع get (& self، key: & Q) القيمة باستخدام المفتاح ويعيد خيارًا. يمكن أن يكون الخيار Some (& V) ، إذا كان المفتاح موجودًا ، فقم بإرجاع مرجع القيمة ، إذا لم يتم العثور على المفتاح في BTreeMap ، فقم بإرجاع بلا.
من ناحية أخرى ، يقوم الفهرس (& self، key: & Q) بإرجاع إشارة مباشرة إلى القيمة المقابلة للمفتاح. ومع ذلك ، فإنه ينطوي على مخاطرة كبيرة: فإنه سيؤدي إلى حالة من الذعر الضمني إذا لم يكن المفتاح موجودًا في BTreeMap. إذا لم يتم التعامل معه بشكل صحيح ، فقد يتعطل البرنامج بشكل غير متوقع ، مما يجعله ثغرة أمنية محتملة.
في الواقع ، طريقة الفهرس (& self، key: & Q) هي التطبيق الأساسي لخاصية std :: ops :: Index. هذه السمة هي عملية مؤشر في سياق غير قابل للتغيير (أي حاوية [index] ) يوفر سكر نحوي مناسب. يمكن للمطورين استخدام btree \ _map مباشرة [key] ، استدعاء طريقة الفهرس (& self ، key: & Q). ومع ذلك ، قد يتجاهلون حقيقة أن هذا الاستخدام قد يؤدي إلى الذعر إذا لم يتم العثور على المفتاح ، مما يشكل تهديدًا ضمنيًا لاستقرار البرنامج.
** الضعف 3: إثارة الذعر الضمني في Sui RPC **
يسمح روتين إصدار وحدة Sui للمستخدمين بإرسال حمولات الوحدة عبر RPC. يستخدم معالج RPC الدالة SuiCommand :: Publish لتفكيك الوحدة النمطية المستلمة مباشرةً قبل إعادة توجيه الطلب إلى شبكة التحقق الخلفية للتحقق من الرمز الثانوي.
أثناء هذا التفكيك ، يتم استخدام قسم الكود \ _وحدة من الوحدة النمطية المرسلة لإنشاء VMControlFlowGraph. تتكون عملية البناء من إنشاء الكتل الأساسية ، والتي يتم تخزينها في خريطة BTreeMap تسمى "كتل". تتضمن العملية إنشاء الخريطة ومعالجتها ، حيث يحدث ذعر ضمني في ظل ظروف معينة.
هنا رمز مبسط:
ذعر ضمني عند إنشاء VMControlFlowGraph
في هذا الكود ، يتم إنشاء VMControlFlowGraph جديد عن طريق السير خلال الكود وإنشاء كتلة أساسية جديدة لكل وحدة رمز. يتم تخزين الكتل الأساسية في كتلة تسمى BTreeMap.
تتم فهرسة مخطط الكتلة باستخدام block [& block] في حلقة تتكرر عبر المكدس ، والتي تمت تهيئتها باستخدام ENTRY \ _BLOCK \ _ID. الافتراض هنا هو وجود ENTRY \ _BLOCK \ _ID واحد على الأقل في خريطة الكتلة.
ومع ذلك ، فإن هذا الافتراض لا يصح دائمًا. على سبيل المثال ، إذا كانت الشفرة المخصصة فارغة ، فسيظل "مخطط الكتلة" فارغًا بعد عملية "إنشاء الكتلة الأساسية". عندما يحاول الكود لاحقًا اجتياز خريطة الكتلة باستخدام نجاحات & blockers [& block] ، فقد يتم إثارة حالة من الذعر الضمني إذا لم يتم العثور على المفتاح. هذا لأن تعبير block [& block] هو في الأساس استدعاء إلى طريقة index () ، والتي ، كما ذكرنا سابقًا ، ستصاب بالذعر إذا لم يكن المفتاح موجودًا في BTreeMap.
يمكن للمهاجم الذي يتمتع بوصول عن بُعد استغلال الثغرة الأمنية في هذه الوظيفة عن طريق إرسال حمولة وحدة نمطية مشوهة بحقل رمز \ _وحدة فارغ. يؤدي طلب RPC البسيط هذا إلى تعطل عملية JSON-RPC بأكملها. إذا استمر المهاجم في إرسال مثل هذه الحمولات المشوهة بأقل جهد ، فسيؤدي ذلك إلى انقطاع مستمر للخدمة. في شبكة blockchain ، هذا يعني أن الشبكة قد لا تكون قادرة على تأكيد المعاملات الجديدة ، مما يؤدي إلى رفض الخدمة (DoS). ستتأثر وظائف الشبكة وثقة المستخدم في النظام بشدة.
إصلاح Sui: إزالة التفكيك من روتين إصدار RPC
من الجدير بالذكر أن CodeUnitVerifier في Move Bytecode Verifier هو المسؤول عن التأكد من أن قسم code \ _unit ليس فارغًا أبدًا. ومع ذلك ، فإن ترتيب العمليات يعرض معالجات RPC لثغرات أمنية محتملة. وذلك لأن عملية التحقق من الصحة تحدث في عقدة Validator ، وهي مرحلة بعد معالجة RPC لوحدات الإدخال النمطية.
استجابةً لهذه المشكلة ، قامت Sui بحل مشكلة عدم الحصانة عن طريق إزالة وظيفة التفكيك في روتين RPC لإصدار الوحدة النمطية. هذه طريقة فعالة لمنع خدمات RPC من معالجة رمز بايت محتمل الخطورة وغير معتمد.
أيضًا ، من الجدير بالذكر أن طرق RPC الأخرى المتعلقة بعمليات البحث عن الكائنات تحتوي أيضًا على إمكانيات التفكيك ، ولكنها ليست عرضة لاستخدام خلايا التعليمات البرمجية الفارغة. هذا لأنهم يقومون دائمًا بالاستعلام عن الوحدات النمطية المنشورة الحالية وتفكيكها. يجب التحقق من الوحدات النمطية المنشورة ، لذا فإن افتراض خلايا التعليمات البرمجية غير الفارغة يظل ثابتًا عند إنشاء VMControlFlowGraph.
** اقتراحات للمطورين **
بعد فهم التهديدات التي يتعرض لها استقرار خدمات RPC في بلوكشين من الذعر الصريح والضمني ، يجب على المطورين إتقان الاستراتيجيات لمنع أو تخفيف هذه المخاطر. يمكن أن تقلل هذه الاستراتيجيات من احتمالية انقطاع الخدمة غير المخطط لها وتزيد من مرونة النظام. لذلك ، يقدم فريق الخبراء في CertiK الاقتراحات التالية ويسردها على أنها أفضل الممارسات لبرمجة Rust.
تجريد ذعر الصدأ: كلما كان ذلك ممكنًا ، ضع في اعتبارك استخدام وظيفة Rust's catch \ _unwind للقبض على حالات الذعر وتحويلها إلى رسائل خطأ. هذا يمنع البرنامج بأكمله من الانهيار ويسمح للمطورين بالتعامل مع الأخطاء بطريقة مسيطر عليها.
استخدم واجهات برمجة التطبيقات بحذر: عادةً ما تحدث حالات الذعر الضمني بسبب إساءة استخدام واجهات برمجة التطبيقات التي توفرها مكتبات قياسية أو مكتبات تابعة لجهات خارجية. لذلك ، من الضروري فهم واجهة برمجة التطبيقات بشكل كامل وتعلم كيفية التعامل مع الأخطاء المحتملة بشكل مناسب. يجب أن يفترض المطورون دائمًا أن واجهات برمجة التطبيقات قد تفشل ويستعدون لمثل هذه المواقف.
المعالجة المناسبة للخطأ: استخدم نوعي النتائج والخيار لمعالجة الأخطاء بدلاً من اللجوء إلى الذعر. يوفر الأول طريقة أكثر تحكمًا في التعامل مع الأخطاء والحالات الخاصة.
أضف الوثائق والتعليقات: تأكد من أن الكود الخاص بك موثق جيدًا وأضف التعليقات إلى الأقسام الهامة (بما في ذلك تلك التي قد يحدث فيها الذعر). سيساعد هذا المطورين الآخرين على فهم المخاطر المحتملة والتعامل معها بفعالية.
لخص
تلعب عقد RPC المستندة إلى الصدأ دورًا مهمًا في أنظمة blockchain مثل Aptos و StarCoin و Sui. نظرًا لاستخدامها لربط DApps و blockchain الأساسي ، فإن موثوقيتها أمر بالغ الأهمية للتشغيل السلس لنظام blockchain. على الرغم من أن هذه الأنظمة تستخدم لغة Rust الآمنة للذاكرة ، إلا أنه لا يزال هناك خطر من سوء التصميم. استكشف فريق البحث في CertiK هذه المخاطر بأمثلة من العالم الواقعي توضح الحاجة إلى تصميم دقيق ودقيق في البرمجة الآمنة للذاكرة.
شاهد النسخة الأصلية
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
تحطم RPC: تحليل نوع جديد من الثغرات الأمنية في عقد Blockchain RPC الآمنة للذاكرة
اكتشف فريق CertiK's Skyfall مؤخرًا نقاط ضعف متعددة في عقد RPC القائمة على Rust في العديد من سلاسل الكتل بما في ذلك Aptos و StarCoin و Sui. نظرًا لأن عقد RPC هي مكونات أساسية للبنية التحتية تربط dApps و blockchain الأساسي ، فإن قوتها أمر بالغ الأهمية للتشغيل السلس. يعرف مصممو Blockchain أهمية خدمات RPC المستقرة ، لذا فهم يعتمدون لغات آمنة للذاكرة مثل Rust لتجنب نقاط الضعف الشائعة التي يمكن أن تدمر عقد RPC.
يساعد اعتماد لغة آمنة للذاكرة مثل Rust عقد RPC على تجنب العديد من الهجمات بناءً على نقاط الضعف في تلف الذاكرة. ومع ذلك ، من خلال مراجعة حديثة ، وجدنا أنه حتى تطبيقات Rust الآمنة للذاكرة ، إذا لم يتم تصميمها وفحصها بعناية ، يمكن أن تكون عرضة لتهديدات أمنية معينة يمكن أن تعطل توفر خدمات RPC.
في هذه المقالة ، سوف نقدم اكتشافنا لسلسلة من نقاط الضعف من خلال الحالات العملية.
** دور عقدة Blockchain RPC **
تعد خدمة استدعاء الإجراء عن بُعد (RPC) الخاصة بـ blockchain هي مكون البنية التحتية الأساسي لـ blockchain من الطبقة الأولى. يوفر للمستخدمين واجهة أمامية مهمة لواجهة برمجة التطبيقات ويعمل كبوابة لشبكة blockchain الخلفية. ومع ذلك ، تختلف خدمة blockchain RPC عن خدمة RPC التقليدية من حيث أنها تسهل تفاعل المستخدم دون المصادقة. يعد التوافر المستمر للخدمة أمرًا بالغ الأهمية ، وأي انقطاع في الخدمة يمكن أن يؤثر بشدة على توافر blockchain الأساسي.
** منظور المراجعة: خادم RPC التقليدي VS blockchain RPC server **
يركز تدقيق خوادم RPC التقليدية بشكل أساسي على التحقق من المدخلات ، والتفويض / المصادقة ، وتزوير الطلب عبر الموقع / تزوير الطلب من جانب الخادم (CSRF / SSRF) ، ونقاط ضعف الحقن (مثل حقن SQL ، وإدخال الأوامر) ، وتسرب المعلومات.
ومع ذلك ، يختلف الوضع بالنسبة لخوادم blockchain RPC. طالما تم توقيع المعاملة ، فلا داعي لمصادقة العميل الطالب في طبقة RPC. باعتبارها الواجهة الأمامية لـ blockchain ، فإن أحد الأهداف الرئيسية لخدمة RPC هو ضمان توفرها. إذا فشلت ، لا يمكن للمستخدمين التفاعل مع blockchain ، ومنعهم من الاستعلام عن البيانات على السلسلة ، أو إرسال المعاملات ، أو إصدار وظائف العقد.
لذلك ، فإن الجانب الأكثر ضعفًا في خادم blockchain RPC هو "التوفر". إذا تعطل الخادم ، يفقد المستخدمون القدرة على التفاعل مع blockchain. والأخطر من ذلك أن بعض الهجمات ستنتشر على السلسلة ، وتؤثر على عدد كبير من العقد ، بل وتؤدي إلى شلل الشبكة بأكملها.
** لماذا ستستخدم blockchain الجديدة RPC الآمنة للذاكرة **
تستخدم بعض سلاسل الكتل ذات الطبقة الأولى المعروفة ، مثل Aptos و Sui ، لغة البرمجة الآمنة للذاكرة Rust لتنفيذ خدمات RPC الخاصة بهم. بفضل الأمان القوي والفحوصات الصارمة لوقت الترجمة ، يجعل Rust البرامج محصنة فعليًا من نقاط الضعف في تلف الذاكرة ، مثل فيض المكدس ، وإحالة المؤشر الفارغ ونقاط الضعف التي لا تحتوي على مرجع لاحق.
لزيادة تأمين قاعدة التعليمات البرمجية ، يتبع المطورون بصرامة أفضل الممارسات ، مثل عدم إدخال تعليمات برمجية غير آمنة. استخدم #! [forbid (unsafe \ _code)] في التعليمات البرمجية المصدر للتأكد من حظر الشفرة غير الآمنة وتصفيتها.
أمثلة على مطوري blockchain الذين ينفذون ممارسات برمجة Rust
لمنع فائض الأعداد الصحيحة ، يستخدم المطورون عادةً وظائف مثل فحص \ _ إضافة ، فحص \ _ فرعي ، تشبع \ _ إضافة ، تشبع \ _ فرعي ، إلخ. بدلاً من الجمع والطرح البسيط (+ ، -). التخفيف من استنفاد الموارد عن طريق تعيين المهلات المناسبة ، وحدود حجم الطلب ، وحدود الطلب على العناصر.
** تهديدات أمان الذاكرة RPC في الطبقة 1 Blockchain **
على الرغم من أنها ليست عرضة لانعدام أمان الذاكرة بالمعنى التقليدي ، إلا أن عُقد RPC تتعرض لمدخلات يتم التلاعب بها بسهولة من قبل المهاجمين. في تطبيق RPC الآمن للذاكرة ، هناك العديد من المواقف التي يمكن أن تؤدي إلى رفض الخدمة. على سبيل المثال ، قد يستنفد تضخيم الذاكرة ذاكرة الخدمة ، بينما قد تؤدي المشكلات المنطقية إلى حدوث حلقات لا نهائية. بالإضافة إلى ذلك ، يمكن أن تشكل ظروف السباق تهديدًا حيث يمكن أن يكون للعمليات المتزامنة تسلسل غير متوقع من الأحداث ، مما يترك النظام في حالة غير محددة. بالإضافة إلى ذلك ، يمكن أن تؤدي التبعيات التي تتم إدارتها بشكل غير صحيح ومكتبات الجهات الخارجية إلى وجود ثغرات أمنية غير معروفة في النظام.
في هذا المنشور ، هدفنا هو لفت الانتباه إلى طرق أكثر إلحاحًا يمكن من خلالها تشغيل حماية وقت تشغيل Rust ، مما يتسبب في إجهاض الخدمات نفسها.
** الذعر الصريح من الصدأ: طريقة لإنهاء خدمات RPC مباشرةً **
يمكن للمطورين إدخال رمز صريح للذعر ، عن قصد أو عن غير قصد. تستخدم هذه الرموز بشكل أساسي للتعامل مع الظروف غير المتوقعة أو الاستثنائية. تتضمن بعض الأمثلة الشائعة ما يلي:
تأكيد! (): استخدم هذا الماكرو عندما يجب استيفاء شرط. إذا فشل الشرط المؤكد ، سينزعج البرنامج ، مما يشير إلى وجود خطأ فادح في الكود.
panic! (): يتم استدعاء هذه الوظيفة عندما يواجه البرنامج خطأ لا يمكنه التعافي منه ولا يمكنه المتابعة.
غير قابل للوصول! (): استخدم هذا الماكرو عندما لا ينبغي تنفيذ جزء من التعليمات البرمجية. إذا تم استدعاء هذا الماكرو ، فإنه يشير إلى خطأ منطقي خطير.
unimplemented! () and todo! (): وحدات الماكرو هذه عبارة عن عناصر نائبة لوظائف غير مطبقة. إذا تم الوصول إلى هذه القيمة ، فسوف يتعطل البرنامج.
undrap (): تُستخدم هذه الطريقة لأنواع الخيار أو النتائج. عند مصادفة متغير Err أو لا شيء ، سيتعطل البرنامج.
** الثغرة 1: تفعيل التأكيد في أداة التحقق من الحركة! **
يستخدم blockchain Aptos مدقق Move bytecode لإجراء تحليل أمان مرجعي من خلال تفسير مجرد لرمز بايت. تعد وظيفة ute () جزءًا من تنفيذ سمة TransferFunctions وتحاكي تنفيذ تعليمات الرمز الثانوي في الكتل الأساسية.
تتمثل مهمة الوظيفة ute \ _inner () في تفسير تعليمات الرمز الثانوي الحالية وتحديث الحالة وفقًا لذلك. إذا قمنا بتنفيذ التعليمات الأخيرة في الكتلة الأساسية ، كما هو موضح بواسطة index == last \ _index ، ستستدعي الوظيفة تأكيد! (self.stack.is \ _empty ()) للتأكد من أن المكدس فارغ. القصد من هذا السلوك هو ضمان أن جميع العمليات متوازنة ، مما يعني أيضًا أن كل دفعة لها فرقعة مقابلة.
في التدفق الطبيعي للتنفيذ ، تكون المكدس دائمًا متوازنة أثناء التفسير المجرد. يتم ضمان ذلك بواسطة Stack Balance Checker ، الذي يتحقق من الرمز الثانوي قبل تفسيره. ومع ذلك ، بمجرد أن نوسع منظورنا إلى مجال المترجمين الفوريين ، نرى أن افتراض توازن المكدس ليس صحيحًا دائمًا.
تصحيح الثغرة الأمنية في دالة التحليل \ _ في AbstractInterpreter
في جوهره ، يحاكي المترجم المجرد الرمز الثانوي على مستوى الكتلة الأساسية. في التطبيق الأصلي ، فإن مواجهة خطأ أثناء ute \ _block ستدفع عملية التحليل لتسجيل الخطأ ومتابعة التنفيذ إلى الكتلة التالية في الرسم البياني لتدفق التحكم. يمكن أن يؤدي هذا إلى إنشاء موقف حيث يمكن أن يتسبب خطأ في كتلة التنفيذ في أن يصبح المكدس غير متوازن. إذا استمر التنفيذ في هذه الحالة ، فسيتم إجراء فحص تأكيد إذا لم يكن المكدس فارغًا ، مما يتسبب في حالة من الذعر.
هذا يعطي المهاجمين فرصة للاستغلال. يمكن للمهاجم تشغيل خطأ من خلال تصميم رمز بايت محدد في ute \ _block () ، ومن ثم قد يقوم ute () بتنفيذ تأكيد إذا لم يكن المكدس فارغًا ، مما يتسبب في فشل التحقق من التأكيد. سيؤدي هذا إلى مزيد من الذعر وإنهاء خدمة RPC ، مما يؤثر على توفرها.
لمنع حدوث ذلك ، يضمن الإصلاح المنفذ إيقاف عملية التحليل بأكملها عندما تواجه وظيفة الحظر / ute خطأ لأول مرة ، وبالتالي تجنب مخاطر الأعطال اللاحقة التي قد تحدث عند استمرار التحليل بسبب عدم توازن المكدس بسبب الأخطاء. يزيل هذا التعديل الظروف التي قد تسبب الذعر ويساعد على تحسين متانة وسلامة المترجم المجرد.
** الضعف 2: إثارة الذعر في StarCoin! **
لدى Starcoin blockchain شوكة خاصة بها لتطبيق Move. في هذا Move repo ، هناك حالة من الذعر في مُنشئ نوع الهيكل! إذا كان تعريف StructDefinition المقدم يحتوي على معلومات الحقل الأصلي ، فسيتم إثارة الذعر بشكل واضح! .
هلع صريح للهياكل المهيأة في إجراءات التطبيع
توجد هذه المخاطر المحتملة في عملية إعادة توزيع الوحدات. إذا كانت الوحدة المنشورة موجودة بالفعل في مخزن البيانات ، فإن تسوية الوحدة النمطية مطلوبة لكل من الوحدة النمطية الحالية ووحدة الإدخال التي يتحكم فيها المهاجم. أثناء هذه العملية ، تقوم وظيفة "normalized :: Module :: new" ببناء هيكل الوحدة النمطية من وحدات الإدخال التي يتحكم فيها المهاجم ، مما يؤدي إلى حدوث "حالة من الذعر!".
المتطلبات الأساسية لروتين التطبيع
يمكن إثارة هذا الذعر من خلال إرسال حمولة مصممة خصيصًا من العميل. لذلك ، يمكن للجهات الضارة تعطيل توفر خدمات RPC.
التصحيح الذعر التهيئة
يقدم تصحيح Starcoin سلوكًا جديدًا للتعامل مع الحالة الأصلية. الآن ، بدلاً من الذعر ، تُرجع قيمة ec فارغة. هذا يقلل من احتمال قيام المستخدمين بإرسال البيانات التي تسبب الذعر.
** الذعر الضمني من الصدأ: طريقة يسهل التغاضي عنها لإنهاء خدمات RPC **
يمكن التعرف بسهولة على حالات الذعر الصريح في التعليمات البرمجية المصدر ، بينما من المرجح أن يتجاهل المطورون حالات الذعر الضمني. عادةً ما تحدث حالات الذعر الضمني عند استخدام واجهات برمجة التطبيقات التي توفرها مكتبات قياسية أو مكتبات تابعة لجهات خارجية. يحتاج المطورون إلى قراءة وثائق API وفهمها تمامًا ، وإلا فقد تتوقف برامج Rust الخاصة بهم بشكل غير متوقع.
ذعر ضمني في BTreeMap
لنأخذ BTreeMap من Rust STD كمثال. BTreeMap عبارة عن هيكل بيانات شائع الاستخدام ينظم أزواج المفتاح والقيمة في شجرة ثنائية مرتبة. يوفر BTreeMap طريقتين لاسترداد القيم بالمفتاح: get (& self، key: & Q) والفهرس (& self، key: & Q).
يسترد التابع get (& self، key: & Q) القيمة باستخدام المفتاح ويعيد خيارًا. يمكن أن يكون الخيار Some (& V) ، إذا كان المفتاح موجودًا ، فقم بإرجاع مرجع القيمة ، إذا لم يتم العثور على المفتاح في BTreeMap ، فقم بإرجاع بلا.
من ناحية أخرى ، يقوم الفهرس (& self، key: & Q) بإرجاع إشارة مباشرة إلى القيمة المقابلة للمفتاح. ومع ذلك ، فإنه ينطوي على مخاطرة كبيرة: فإنه سيؤدي إلى حالة من الذعر الضمني إذا لم يكن المفتاح موجودًا في BTreeMap. إذا لم يتم التعامل معه بشكل صحيح ، فقد يتعطل البرنامج بشكل غير متوقع ، مما يجعله ثغرة أمنية محتملة.
في الواقع ، طريقة الفهرس (& self، key: & Q) هي التطبيق الأساسي لخاصية std :: ops :: Index. هذه السمة هي عملية مؤشر في سياق غير قابل للتغيير (أي حاوية [index] ) يوفر سكر نحوي مناسب. يمكن للمطورين استخدام btree \ _map مباشرة [key] ، استدعاء طريقة الفهرس (& self ، key: & Q). ومع ذلك ، قد يتجاهلون حقيقة أن هذا الاستخدام قد يؤدي إلى الذعر إذا لم يتم العثور على المفتاح ، مما يشكل تهديدًا ضمنيًا لاستقرار البرنامج.
** الضعف 3: إثارة الذعر الضمني في Sui RPC **
يسمح روتين إصدار وحدة Sui للمستخدمين بإرسال حمولات الوحدة عبر RPC. يستخدم معالج RPC الدالة SuiCommand :: Publish لتفكيك الوحدة النمطية المستلمة مباشرةً قبل إعادة توجيه الطلب إلى شبكة التحقق الخلفية للتحقق من الرمز الثانوي.
أثناء هذا التفكيك ، يتم استخدام قسم الكود \ _وحدة من الوحدة النمطية المرسلة لإنشاء VMControlFlowGraph. تتكون عملية البناء من إنشاء الكتل الأساسية ، والتي يتم تخزينها في خريطة BTreeMap تسمى "كتل". تتضمن العملية إنشاء الخريطة ومعالجتها ، حيث يحدث ذعر ضمني في ظل ظروف معينة.
هنا رمز مبسط:
ذعر ضمني عند إنشاء VMControlFlowGraph
في هذا الكود ، يتم إنشاء VMControlFlowGraph جديد عن طريق السير خلال الكود وإنشاء كتلة أساسية جديدة لكل وحدة رمز. يتم تخزين الكتل الأساسية في كتلة تسمى BTreeMap.
تتم فهرسة مخطط الكتلة باستخدام block [& block] في حلقة تتكرر عبر المكدس ، والتي تمت تهيئتها باستخدام ENTRY \ _BLOCK \ _ID. الافتراض هنا هو وجود ENTRY \ _BLOCK \ _ID واحد على الأقل في خريطة الكتلة.
ومع ذلك ، فإن هذا الافتراض لا يصح دائمًا. على سبيل المثال ، إذا كانت الشفرة المخصصة فارغة ، فسيظل "مخطط الكتلة" فارغًا بعد عملية "إنشاء الكتلة الأساسية". عندما يحاول الكود لاحقًا اجتياز خريطة الكتلة باستخدام نجاحات & blockers [& block] ، فقد يتم إثارة حالة من الذعر الضمني إذا لم يتم العثور على المفتاح. هذا لأن تعبير block [& block] هو في الأساس استدعاء إلى طريقة index () ، والتي ، كما ذكرنا سابقًا ، ستصاب بالذعر إذا لم يكن المفتاح موجودًا في BTreeMap.
يمكن للمهاجم الذي يتمتع بوصول عن بُعد استغلال الثغرة الأمنية في هذه الوظيفة عن طريق إرسال حمولة وحدة نمطية مشوهة بحقل رمز \ _وحدة فارغ. يؤدي طلب RPC البسيط هذا إلى تعطل عملية JSON-RPC بأكملها. إذا استمر المهاجم في إرسال مثل هذه الحمولات المشوهة بأقل جهد ، فسيؤدي ذلك إلى انقطاع مستمر للخدمة. في شبكة blockchain ، هذا يعني أن الشبكة قد لا تكون قادرة على تأكيد المعاملات الجديدة ، مما يؤدي إلى رفض الخدمة (DoS). ستتأثر وظائف الشبكة وثقة المستخدم في النظام بشدة.
إصلاح Sui: إزالة التفكيك من روتين إصدار RPC
من الجدير بالذكر أن CodeUnitVerifier في Move Bytecode Verifier هو المسؤول عن التأكد من أن قسم code \ _unit ليس فارغًا أبدًا. ومع ذلك ، فإن ترتيب العمليات يعرض معالجات RPC لثغرات أمنية محتملة. وذلك لأن عملية التحقق من الصحة تحدث في عقدة Validator ، وهي مرحلة بعد معالجة RPC لوحدات الإدخال النمطية.
استجابةً لهذه المشكلة ، قامت Sui بحل مشكلة عدم الحصانة عن طريق إزالة وظيفة التفكيك في روتين RPC لإصدار الوحدة النمطية. هذه طريقة فعالة لمنع خدمات RPC من معالجة رمز بايت محتمل الخطورة وغير معتمد.
أيضًا ، من الجدير بالذكر أن طرق RPC الأخرى المتعلقة بعمليات البحث عن الكائنات تحتوي أيضًا على إمكانيات التفكيك ، ولكنها ليست عرضة لاستخدام خلايا التعليمات البرمجية الفارغة. هذا لأنهم يقومون دائمًا بالاستعلام عن الوحدات النمطية المنشورة الحالية وتفكيكها. يجب التحقق من الوحدات النمطية المنشورة ، لذا فإن افتراض خلايا التعليمات البرمجية غير الفارغة يظل ثابتًا عند إنشاء VMControlFlowGraph.
** اقتراحات للمطورين **
بعد فهم التهديدات التي يتعرض لها استقرار خدمات RPC في بلوكشين من الذعر الصريح والضمني ، يجب على المطورين إتقان الاستراتيجيات لمنع أو تخفيف هذه المخاطر. يمكن أن تقلل هذه الاستراتيجيات من احتمالية انقطاع الخدمة غير المخطط لها وتزيد من مرونة النظام. لذلك ، يقدم فريق الخبراء في CertiK الاقتراحات التالية ويسردها على أنها أفضل الممارسات لبرمجة Rust.
تجريد ذعر الصدأ: كلما كان ذلك ممكنًا ، ضع في اعتبارك استخدام وظيفة Rust's catch \ _unwind للقبض على حالات الذعر وتحويلها إلى رسائل خطأ. هذا يمنع البرنامج بأكمله من الانهيار ويسمح للمطورين بالتعامل مع الأخطاء بطريقة مسيطر عليها.
استخدم واجهات برمجة التطبيقات بحذر: عادةً ما تحدث حالات الذعر الضمني بسبب إساءة استخدام واجهات برمجة التطبيقات التي توفرها مكتبات قياسية أو مكتبات تابعة لجهات خارجية. لذلك ، من الضروري فهم واجهة برمجة التطبيقات بشكل كامل وتعلم كيفية التعامل مع الأخطاء المحتملة بشكل مناسب. يجب أن يفترض المطورون دائمًا أن واجهات برمجة التطبيقات قد تفشل ويستعدون لمثل هذه المواقف.
المعالجة المناسبة للخطأ: استخدم نوعي النتائج والخيار لمعالجة الأخطاء بدلاً من اللجوء إلى الذعر. يوفر الأول طريقة أكثر تحكمًا في التعامل مع الأخطاء والحالات الخاصة.
أضف الوثائق والتعليقات: تأكد من أن الكود الخاص بك موثق جيدًا وأضف التعليقات إلى الأقسام الهامة (بما في ذلك تلك التي قد يحدث فيها الذعر). سيساعد هذا المطورين الآخرين على فهم المخاطر المحتملة والتعامل معها بفعالية.
لخص
تلعب عقد RPC المستندة إلى الصدأ دورًا مهمًا في أنظمة blockchain مثل Aptos و StarCoin و Sui. نظرًا لاستخدامها لربط DApps و blockchain الأساسي ، فإن موثوقيتها أمر بالغ الأهمية للتشغيل السلس لنظام blockchain. على الرغم من أن هذه الأنظمة تستخدم لغة Rust الآمنة للذاكرة ، إلا أنه لا يزال هناك خطر من سوء التصميم. استكشف فريق البحث في CertiK هذه المخاطر بأمثلة من العالم الواقعي توضح الحاجة إلى تصميم دقيق ودقيق في البرمجة الآمنة للذاكرة.