كيفية العثور على 10 مليون دولار فقط عن طريق قراءة Blockchain

منذ أسبوعين ، أبلغ أحد المتحمسين لـ Golem وحامل GNT عن وجود خطأ غريب في معاملة نقل GNT. بعد التحقق من البيانات المرفقة بالصفقة ، اكتشفت أنه يجب أن تكون هناك مشكلة في طريقة إعداد البورصة لبيانات المعاملة. "أوه لا ،" اعتقدت ، "يمكن استخدام هذا الخطأ لتفريغ حساب GNT بأكمله في البورصة!" وتم تخزين عدد كبير من الرموز هناك!

كان هذا الخطأ هو في الواقع خطأ البورصة ، ولكنه كان مرتبطًا أيضًا بالطريقة التي ترى بها عقود Ethereum بيانات إدخال المعاملة و Solidity ABI (على سبيل المثال الطريقة التي ترمز بها أساليب عقود Solidity إلى ترميز الحجج). بالطبع ، لم يكن الأمر متعلقًا بـ GNT ، ولكن في الواقع لجميع رموز ERC20 ، بالإضافة إلى العقود الأخرى التي لها طرق شبيهة بالنقل. نعم ، لقد قرأت ذلك بشكل صحيح: يمكن أن يعمل ذلك مع أي رمز مميز يستند إلى Ethereum مدرج في البورصة المذكورة ، إذا تمت إدارة عمليات السحب فقط بنفس طريقة GNT. لا نعرف أن هذا هو الحال ، لكننا نفترض أنه كان من المحتمل جدًا.

Ethereum Contract ABI

عقود Ethereum الخام ليس لها طرق ولا وظائف. الطرق عبارة عن ميزات للغات عالية المستوى مثل Solidity ، وهي تستخدم Ethereum Contract ABI لتحديد كيفية تقسيم الرمز الفرعي للعقد إلى طرق ، وكذلك كيفية تشفير أنواع مختلفة من الوسائط في بيانات إدخال المعاملة. (راجع https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI للحصول على مرجع.)

لاستدعاء طريقة النقل (عنوان a ، uint v) من عقد GNT لنقل 1 GNT إلى عنوان 0xabcabcabcabcabcabcabcabcabcabcabcabcabca ، يحتاج المرء إلى تضمين 3 أجزاء من البيانات:

  • 4 بايت ، كونها معرّف الطريقة: a9059cbb
  • 32 بايت ، مع عنوان الوجهة (20 بايت) مليئة بالأصفار البادئة: 00000000000000000000000000ccabcabcabcabcabcabcabcabcabcabcabcabca
  • 32 بايت ، القيمة المنقولة ، 1 * 10 1 GNT: 0000000000000000000000000000000000000000000000000de0b6b3a7640000

ستبدو المعاملة الكاملة هكذا: a9059cbb0000000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca00000000000000000000000000000000000000000000000000000de0b6b3a7640000.

بيانات إدخال المعاملة غير محدودة

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

الحشرة

افترضت الخدمة التي تقوم بإعداد البيانات لعمليات النقل الرمزية أن المستخدمين سوف يقومون بإدخال عناوين طويلة 20 بايت ، ولكن لم يتم التحقق من طول العناوين بالفعل. في المعاملة المذكورة أعلاه ، قام المستخدم بملء عنوان غير صالح بطول أقصر: 79735. تم تشويه البيانات الناتجة نظرًا لأن وسيطة العنوان استغرقت 14.5 بايت (12 بايت لظهور الأصفار + 4.5 بايت من إدخال المستخدم). للتأكد من دقتها ، كانت بيانات المعاملة جيدة لمنصة Ethereum لأنها لا تهتم بالبيانات المضمنة في المعاملات باستثناء تطبيق رسوم لكل بايت. السبب الوحيد لعدم تنفيذ عملية نقل الرمز المميز بواسطة عقد GNT هو أن المبلغ في المعاملة كان مرتفعًا بشكل يبعث على السخرية (أعلى من إجمالي العرض ، وبطبيعة الحال أعلى من رصيد العنوان المعني). كان صاحب العنوان محظوظًا جدًا لأن المستخدم استخدم سلسلة قصيرة من هذا العنوان: مع بعض الحظ (السيئ) ، سيكون المستخدم قادرًا على * عرضيًا * إفراغ عنوان كل GNT وإرساله إلى بعض عشوائي عنوان. هذا عندما أدركنا أن الأخطاء يمكن أن تستخدم أيضًا للهجوم ، وكانت خطيرة جدًا.

الهجوم المحتمل

كما قد تلاحظ ، يؤدي السماح للمستخدم بإدخال عنوان نقل أقصر إلى تحويل قيمة "كمية الرموز التي يجب نقلها" إلى اليسار ، مما يجعل القيمة أكبر. من السهل جدًا العثور على مفتاح خاص لعنوان Ethereum مع أصفار في نهاية العنوان ، على سبيل المثال 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000.

لذلك ، يمكن لمالك هذا العنوان إدخال 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (تخطي الأصفار) في واجهة الخدمة. يمكن للمهاجم بعد ذلك طلب نقل بعض القيمة X من الخدمة ، إلى العنوان المشوه التالف. هذا سيؤدي في الواقع إلى نقل قيمة تحولت من 16 بت ، أي 65536 مرة أكبر من X ، إلى حساب Ethereum للمهاجمين!

ماذا فعلنا حيال ذلك؟

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

ماذا يمكن أن تفعل Ethereum حول هذا؟

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

ماذا ينبغي أن تفعل التبادلات بشأن هذا؟

  1. تحقق من إدخال المستخدم بصرامة قدر الإمكان. ببساطة التحقق من طول عنوان المقدمة من قبل المستخدم يؤمن لهم من الهجوم الموصوف. علاوة على ذلك ، تحقق من صحة المجموع الاختباري لعنوان Ethereum إذا كان ذلك متاحًا (انظر EIP55) ، أو حتى قبول العناوين حصريًا مع المجموع الاختباري. هذا يزيد من كل من الأمن وسهولة الاستخدام.
  2. تأكد من أن بيانات المعاملة مشفرة بشكل صحيح.
  3. قد يتم تحليل بيانات المعاملة التي تم إنشاؤها أيضًا والتحقق منها مقابل إدخال المستخدم المحدد.
  4. تحقق مما إذا كانت المعلمات الأخرى مثل الغاز وسعر الغاز وعنوان الوجهة للمعاملة المولدة تتوافق مع القيم المتوقعة.