أخبارالمطورينإدخالالمشروع Blockchain شرح الأحداث والمؤتمرات الصحافةالنشرات الإخبارية
اشترك في نشرتنا الإخبارية.
عنوان بريد الكتروني
نحن نحترم خصوصيتك
الرئيسيةالمدونةتطوير Blockchain
توصيات أمان عقد Ethereum الذكية
من كيفية التعامل مع المكالمات الخارجية إلى مخططات الالتزام ، إليك أكثر من 10 أنماط أمان ذكية للعقود يجب اتباعها عند البناء على Ethereum.by ConsenSys 10 يوليو 2020 نُشر في 10 يوليو 2020
كما غطينا في Smart Contract Security Mindset ، فإن مطور Ethereum اليقظ يحتفظ دائمًا بخمسة مبادئ في الاعتبار:
- استعد للفشل
- طرح بعناية
- حافظ على بساطة العقود
- ابق على اطلاع
- كن على دراية بخصوصيات EVM
في هذا المنشور ، سنتعمق في خصوصيات EVM ونستعرض قائمة بالأنماط التي يجب عليك اتباعها عند تطوير أي نظام عقد ذكي على Ethereum. هذه القطعة مخصصة بشكل أساسي لمطوري Ethereum المتوسطين. إذا كنت لا تزال في المراحل الأولى من الاستكشاف ، فراجع برنامج مطوري blockchain التابع لأكاديمية ConsenSys.
حسنًا ، دعنا نتعمق.
المكالمات الخارجية
توخ الحذر عند إجراء مكالمات خارجية
يمكن أن تؤدي استدعاءات العقود الذكية غير الموثوق بها إلى العديد من المخاطر أو الأخطاء غير المتوقعة. قد تنفذ المكالمات الخارجية تعليمات برمجية ضارة في هذا العقد أو أي عقد آخر تعتمد عليه. لذلك ، تعامل مع كل مكالمة خارجية على أنها مخاطر أمنية محتملة. عندما لا يكون من الممكن أو غير المرغوب فيه إزالة المكالمات الخارجية ، استخدم التوصيات في بقية هذا القسم لتقليل الخطر.
ضع علامة على العقود غير الموثوق بها
عند التفاعل مع العقود الخارجية ، قم بتسمية المتغيرات والأساليب وواجهات العقد بطريقة توضح أن التفاعل معها قد يكون غير آمن. هذا ينطبق على وظائفك الخاصة التي تستدعي العقود الخارجية.
// Bad Bank.withdraw (100) ؛ // غير واضح ما إذا كانت الوظيفة الموثوقة أو غير الموثوق بها تجعل السحب (مبلغ غير موثوق به) {// ليس من الواضح أن هذه الوظيفة قد تكون غير آمنة Bank.withdraw (المبلغ) ؛ } // good UntrustedBank.withdraw (100) ؛ // استدعاء خارجي غير موثوق به TrustedBank.withdraw (100) ؛ // عقد مصرفي خارجي ولكن موثوق به تحتفظ به وظيفة XYZ Corp makeUntrustedWithdrawal (مبلغ غير مستخدم) {UntrustedBank.withdraw (المبلغ) ؛ } لغة الكود: PHP (php)
تجنب تغييرات الحالة بعد المكالمات الخارجية
سواء كنت تستخدم استدعاءات أولية (من النموذج someAddress.call ()) أو استدعاءات تعاقدية (من النموذج ExternalContract.someMethod ()) ، افترض أن التعليمات البرمجية الخبيثة قد يتم تنفيذها. حتى إذا لم يكن ExternalContract غير ضار ، يمكن تنفيذ التعليمات البرمجية الضارة من خلال أي عقود تستدعيها.
أحد المخاطر الخاصة هو أن الشفرة الخبيثة قد تخطف تدفق التحكم ، مما يؤدي إلى نقاط ضعف بسبب عودة الدخول. (نرى عودة لمناقشة أشمل لهذه المشكلة).
إذا كنت تجري مكالمة لعقد خارجي غير موثوق به ، فتجنب تغييرات الحالة بعد المكالمة. يُطلق على هذا النمط أحيانًا اسم نمط الشيكات والتأثيرات والتفاعلات.
نرى SWC-107
لا تستخدم التحويل () أو الإرسال ().
.نقل () وإرسال () بالضبط 2300 غاز إلى المستلم. كان الهدف من راتب الغاز الثابت هذا هو منع حدوث ذلك نقاط ضعف إعادة الدخول, لكن هذا منطقي فقط في ظل افتراض أن تكاليف الغاز ثابتة. EIP 1884, التي كانت جزءًا من مفترق الطرق في إسطنبول ، زادت من تكلفة الغاز لعملية SLOAD. تسبب هذا في تكلف الوظيفة الاحتياطية للعقد أكثر من 2300 غاز. نوصي بالتوقف عن استخدام .ransfer () and.send () وبدلاً من ذلك use.call ().
// عقد سيئ ضعيف {وظيفة سحب (مبلغ uint256) خارجي {// هذا يعيد توجيه 2300 غاز ، والذي قد لا يكون كافيًا إذا كان المستلم // عقدًا وتغيرت تكاليف الغاز. msg.sender.transfer (المبلغ) ؛ }} // عقد جيد ثابت {وظيفة سحب (مبلغ uint256) خارجي {// هذا يعيد توجيه كل الغاز المتاح. تأكد من التحقق من قيمة الإرجاع! (نجاح منطقي ،) = msg.sender.call.value (المبلغ) ("") ؛ تتطلب (النجاح, "فشل النقل.") ؛ }} لغة الشفرة: JavaScript (javascript)
لاحظ أن call () لا يفعل شيئًا للتخفيف من هجمات العودة ، لذلك يجب اتخاذ احتياطات أخرى. لمنع هجمات إعادة الدخول ، استخدم ملحق نمط الشيكات والتأثيرات والتفاعلات.
معالجة الأخطاء في المكالمات الخارجية
تقدم Solidity طرق اتصال منخفضة المستوى تعمل على العناوين الأولية: address.call () و address.callcode () و address.delegatecall () و address.send (). لا تطرح هذه الطرق ذات المستوى المنخفض استثناءً أبدًا ، ولكنها ستعيد خطأ إذا واجهت الاستدعاء استثناءً. من ناحية أخرى ، ستنشر استدعاءات العقد (على سبيل المثال ، ExternalContract.doSomething ()) رمية (على سبيل المثال ، ExternalContract.doSomething () ستلقي أيضًا إذا رميت doSomething ()).
إذا اخترت استخدام طرق الاتصال ذات المستوى المنخفض ، فتأكد من التعامل مع احتمال فشل المكالمة ، عن طريق التحقق من القيمة المرتجعة.
// bad someAddress.send (55) ؛ someAddress.call.value (55) ("") ؛ // هذا خطر مضاعف ، لأنه سيعيد توجيه كل الغاز المتبقي ولا يتحقق من النتيجة someAddress.call.value (100) (bytes4 (sha3 ("الوديعة()"))) ؛ // إذا أدى الإيداع إلى استثناء ، فإن المكالمة الأولية () ستعيد فقط false ولن يتم إرجاع المعاملة // good (نجاح منطقي ،) = someAddress.call.value (55) ("") ؛ if (! success) {// handle failure code} ExternalContract (someAddress) .deposit.value (100) () ؛ لغة الكود: JavaScript (javascript)
نرى SWC-104
يفضل دفع السحب للمكالمات الخارجية
يمكن أن تفشل المكالمات الخارجية عن طريق الخطأ أو عن عمد لتقليل الضرر الناجم عن مثل هذه الإخفاقات ، من الأفضل غالبًا عزل كل مكالمة خارجية في معاملتها الخاصة التي يمكن أن يبدأها متلقي المكالمة. هذا مهم بشكل خاص للمدفوعات ، حيث من الأفضل السماح للمستخدمين بسحب الأموال بدلاً من دفع الأموال إليهم تلقائيًا. (هذا يقلل أيضًا من فرصة مشاكل حد الغاز.) تجنب الجمع بين عدة تحويلات إيثر في معاملة واحدة.
// مزاد عقد سيئ {عنوان أعلى عرض سعر؛ uint topBid ؛ دالة العطاء () مستحقة الدفع {تتطلب (msg.value >= السعر الأعلى) ؛ إذا (topBidder! = العنوان (0)) {(نجاح منطقي ،) = topBidder.call.value (mostBid) ("") ؛ تتطلب (النجاح) ؛ // إذا فشلت هذه المكالمة باستمرار ، فلن يتمكن أي شخص آخر من المزايدة} mostBidder = msg.sender؛ topBid = msg.value ؛ }} // good contract auction {address mostBidder؛ uint topBid ؛ تعيين (العنوان => uint) المبالغ المستردة ؛ وظيفة العطاء () مستحقة الدفع الخارجية {تتطلب (msg.value >= السعر الأعلى) ؛ if (mostBidder! = address (0)) {المبالغ المستردة [topBidder] + = topBid؛ // سجل المبلغ المسترد الذي يمكن لهذا المستخدم المطالبة به} mostBidder = msg.sender؛ topBid = msg.value ؛ } function pullRefund () external {uint refund = refunds [msg.sender]؛ المبالغ المستردة [msg.sender] = 0 ؛ (نجاح منطقي ،) = msg.sender.call.value (استرداد) ("") ؛ تتطلب (النجاح) ؛ }} لغة الشفرة: JavaScript (javascript)
نرى SWC-128
لا تفوض الاتصال برمز غير موثوق به
تستدعي وظيفة الاستدعاء المفوض الوظائف من العقود الأخرى كما لو كانت تنتمي إلى عقد المتصل. وبالتالي قد يغير المستدعي حالة العنوان المتصل. قد يكون هذا غير آمن. يوضح المثال أدناه كيف يمكن أن يؤدي استخدام مندوب الاتصال إلى إتلاف العقد وفقدان رصيده.
إتلاف العقد {function doWork () خارجي {selfdestruct (0)؛ }} عامل العقد {function doWork (address _internalWorker) public {// unsafe _internalWorker.delegatecall (bytes4 (keccak256 ("اعمل()"))) ؛ }} لغة الشفرة: JavaScript (javascript)
إذا تم استدعاء Worker.doWork () بعنوان عقد Destructor الذي تم نشره كوسيطة ، فسيتم إتلاف عقد العامل ذاتيًا. تفويض التنفيذ فقط للعقود الموثوقة ، و أبدا إلى عنوان قدمه المستخدم.
تحذير
لا تفترض أن العقود قد تم إنشاؤها برصيد صفري. يمكن للمهاجم إرسال الأثير إلى عنوان العقد قبل إنشائه. يجب ألا تفترض العقود أن حالتها الأولية تحتوي على رصيد صفري. نرى العدد 61 لمزيد من التفاصيل.
نرى SWC-112
تذكر أنه يمكن إجبار الأثير على إرسال حساب
احذر من ترميز الثابت الذي يتحقق بدقة من ميزان العقد.
يمكن للمهاجم إرسال الأثير بالقوة إلى أي حساب. لا يمكن منع هذا (ولا حتى مع وجود وظيفة احتياطية تقوم بإرجاع ()).
يمكن للمهاجم القيام بذلك عن طريق إنشاء عقد ، وتمويله بـ 1 وي ، واستدعاء التدمير الذاتي (ضحية العنوان). لم يتم استدعاء أي رمز في عنوان الضحية ، لذلك لا يمكن منعه. هذا صحيح أيضًا بالنسبة لمكافأة الكتلة التي يتم إرسالها إلى عنوان المُعدِّن ، والذي يمكن أن يكون أي عنوان تعسفي.
أيضًا ، نظرًا لأنه يمكن حساب عناوين العقد مسبقًا ، يمكن إرسال الأثير إلى عنوان قبل نشر العقد.
نرى SWC-132
تذكر أن البيانات على السلسلة عامة
تتطلب العديد من التطبيقات أن تكون البيانات المقدمة خاصة حتى وقت ما لكي تعمل. الألعاب (على سبيل المثال ، مقص ورق الصخر على السلسلة) وآليات المزاد (مثل العطاء المختوم مزادات فيكري) فئتان رئيسيتان من الأمثلة. إذا كنت تقوم بإنشاء تطبيق تكون الخصوصية فيه مشكلة ، فتأكد من تجنب مطالبة المستخدمين بنشر المعلومات في وقت مبكر جدًا. أفضل استراتيجية لاستخدام مخططات الالتزام بمراحل منفصلة: الالتزام أولاً باستخدام تجزئة القيم وفي مرحلة لاحقة الكشف عن القيم.
أمثلة:
- في مقص ورق الصخر ، اطلب من كلا اللاعبين تقديم تجزئة لحركتهم المقصودة أولاً ، ثم اطلب من كلا اللاعبين إرسال حركتهما ؛ إذا كانت الحركة المقدمة لا تتطابق مع التجزئة ، فقم بإزالتها.
- في المزاد ، اطلب من اللاعبين تقديم تجزئة لقيمة مزايدتهم في مرحلة أولية (جنبًا إلى جنب مع وديعة أكبر من قيمة مزايدتهم) ، ثم إرسال قيمة مزايدة المزاد في المرحلة الثانية.
- عند تطوير تطبيق يعتمد على مولد أرقام عشوائي ، يجب أن يكون الترتيب دائمًا (1) يقوم اللاعبون بإرسال الحركات ، (2) يتم إنشاء رقم عشوائي ، (3) يتم الدفع للاعبين. كثير من الناس يبحثون بنشاط عن مولدات الأرقام العشوائية ؛ تتضمن أفضل الحلول الحالية في فئتها رؤوس كتل Bitcoin (تم التحقق منها من خلال http://btcrelay.org) ، مخططات التجزئة-الالتزام-الكشف (على سبيل المثال ، يقوم أحد الأطراف بإنشاء رقم ، ونشر التجزئة الخاصة به “للالتزام” بالقيمة ، ثم الكشف عن القيمة لاحقًا) و رانداو. نظرًا لأن Ethereum هو بروتوكول حتمي ، فلا يمكنك استخدام أي متغير داخل البروتوكول كرقم عشوائي غير متوقع. انتبه أيضًا إلى أن المعدنين يتحكمون إلى حد ما في قيمة block.blockhash ()*.
احذر من احتمال أن بعض المشاركين قد “يتوقفون عن العمل” ولا يعودون
لا تجعل عمليات استرداد الأموال أو المطالبات تعتمد على قيام طرف معين بإجراء معين دون أي طريقة أخرى للحصول على الأموال. على سبيل المثال ، في لعبة Rock-paper-scissors ، أحد الأخطاء الشائعة هو عدم تقديم تعويضات حتى يقوم كلا اللاعبين بإرسال تحركاتهم ؛ ومع ذلك ، يمكن للاعب الخبيث أن “يحزن” الآخر من خلال عدم تقديم نقلته مطلقًا – في الواقع ، إذا رأى اللاعب النقلة التي تم الكشف عنها للاعب الآخر وقرر أنه خسر ، فلن يكون لديه سبب لتقديم نقلته الخاصة على الإطلاق. قد تنشأ هذه القضية أيضًا في سياق تسوية قناة الدولة. عندما تكون مثل هذه المواقف مشكلة ، (1) توفير طريقة للتحايل على المشاركين غير المشاركين ، ربما خلال فترة زمنية محددة ، و (2) النظر في إضافة حافز اقتصادي إضافي للمشاركين لتقديم المعلومات في جميع المواقف التي هم فيها من المفترض أن تفعل ذلك.
احذر من نفي العدد الصحيح الأكثر سلبية
يوفر Solidity عدة أنواع للعمل مع الأعداد الصحيحة الموقعة. كما هو الحال في معظم لغات البرمجة ، في Solidity ، يمكن أن يمثل عدد صحيح مع إشارة N بتات القيم من -2 ^ (N-1) إلى 2 ^ (N-1) -1. هذا يعني أنه لا يوجد مكافئ إيجابي لـ MIN_INT. يتم تنفيذ النفي على أنه إيجاد مكمل الرقمين ، وبالتالي نفي الرقم الأكثر سالب سوف ينتج عنه نفس الرقم. هذا صحيح لجميع أنواع الأعداد الصحيحة الموقعة في Solidity (int8 ، int16 ، … ، int256).
نفي العقد {function Negate8 (int8 _i) عوائد نقية عامة (int8) {return -_i؛ } التابع negate16 (int16 _i) العوائد الصافية العامة (int16) {return -_i؛ } int8 public a = negate8 (-128) ؛ // -128 int16 public b = negate16 (-128) ؛ // 128 int16 public c = negate16 (-32768) ؛ // -32768} لغة الكود: PHP (php)
تتمثل إحدى طرق التعامل مع هذا في التحقق من قيمة المتغير قبل النفي ورمي ما إذا كان يساوي MIN_INT. خيار آخر هو التأكد من أن الرقم الأكثر سالبة لن يتحقق أبدًا باستخدام نوع بسعة أعلى (على سبيل المثال ، int32 بدلاً من int16).
تحدث مشكلة مماثلة مع أنواع int عندما يتم ضرب MIN_INT أو قسمة على -1.
هل كود blockchain الخاص بك آمن?
نأمل أن تكون هذه التوصيات مفيدة. إذا كنت أنت وفريقك تستعدان للإطلاق أو حتى في بداية دورة حياة التطوير وتحتاجان إلى التحقق من سلامة عقودك الذكية ، فلا تتردد في التواصل مع فريق مهندسي الأمن لدينا في ConsenSys Diligence. نحن هنا لمساعدتك في إطلاق تطبيقات Ethereum الخاصة بك والحفاظ عليها بثقة 100٪.
احجز فحصًا أمنيًا
احجز مراجعة ليوم واحد مع فريق خبراء أمان blockchain لدينا. احجز لك اليوم SecuritySmart Contracts النشرة الإخبارية اشترك في النشرة الإخبارية للحصول على أحدث أخبار Ethereum وحلول المؤسسات وموارد المطور والمزيد.ندوة عبر الإنترنت
كيفية بناء منتج Blockchain ناجح
ندوة عبر الإنترنت
كيفية إعداد وتشغيل عقدة إيثريوم
ندوة عبر الإنترنت
كيفية بناء Ethereum API الخاصة بك
ندوة عبر الإنترنت
كيفية إنشاء رمز اجتماعي
ندوة عبر الإنترنت
استخدام أدوات الأمن في تطوير العقود الذكية
ندوة عبر الإنترنت