BTC
ETH
HTX
SOL
BNB
ดูตลาด
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

ต้องอ่านการพัฒนาสัญญาอัจฉริยะ: ปัญหาด้านความปลอดภัย Solidity 10 ข้อนี้ไม่สามารถเพิกเฉยได้

登链社区
特邀专栏作者
บทความนี้มีประมาณ 3645 คำ การอ่านทั้งหมดใช้เวลาประมาณ 6 นาที
ความปลอดภัยของสัญญาอัจฉริยะกำลังเป็นกังวล เรียนรู้เกี่ยวกับปัญหาด้านความปลอดภัยทั่วไป
สรุปโดย AI
ความปลอดภัยของสัญญาอัจฉริยะกำลังเป็นกังวล เรียนรู้เกี่ยวกับปัญหาด้านความปลอดภัยทั่วไป

หมายเหตุบรรณาธิการ: บทความนี้มาจากชุมชนเติ้งเหลียนพิมพ์ซ้ำโดย Odaily โดยได้รับอนุญาต

หมายเหตุบรรณาธิการ: บทความนี้มาจาก

ชุมชนเติ้งเหลียน

ชุมชนเติ้งเหลียน

พิมพ์ซ้ำโดย Odaily โดยได้รับอนุญาต

ในปี 2018 เรา (CheckMarx) ได้ทำการศึกษาเบื้องต้นเกี่ยวกับสถานะของการรักษาความปลอดภัยของสัญญาอัจฉริยะ โดยมุ่งเน้นไปที่สัญญาอัจฉริยะที่เขียนด้วย Solidity[1] ในขณะนั้น เราได้รวบรวมปัญหาด้านความปลอดภัยของสัญญาอัจฉริยะ 10 อันดับแรกโดยอ้างอิงจากซอร์สโค้ดของสัญญาที่เปิดเผยต่อสาธารณะ สองปีผ่านไป ถึงเวลาอัปเดตการวิจัยและประเมินความก้าวหน้าของการรักษาความปลอดภัยสัญญาอัจฉริยะ

ปัญหาอื่น ๆ ที่ควรค่าแก่ความสนใจ

แม้ว่าการจัดลำดับปัญหาด้านความปลอดภัยจะเป็นเรื่องที่ดี แต่ก็มักจะมีรายละเอียดที่น่าสนใจ เนื่องจากรายละเอียดบางอย่างไม่สอดคล้องกับรายการจัดอันดับ ก่อนที่จะเจาะลึกลงไปถึง 10 ประเด็นหลัก จำเป็นต้องอธิบายประเด็นเด่นบางประการของงานวิจัยต้นฉบับที่สมควรได้รับความสนใจ:

ในปี 2561 ประเด็นสำคัญ 2 อันดับแรกคือการปฏิเสธสัญญาบริการภายนอกและการกลับเข้าที่ใหม่ แต่ตอนนี้ปัญหาเหล่านี้คลี่คลายลงแล้ว (แต่ก็ยังน่ากังวลอยู่) คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับ Reentrancy ได้จากบล็อกการวิจัยของเรา: มุมมองด้านความปลอดภัยเกี่ยวกับสัญญาอัจฉริยะ [2]

หมายเหตุผู้แปล: อันที่จริง เนื่องจากมีการผสมผสานระหว่างแอปพลิเคชัน DeFi (เช่น สินเชื่อแฟลช) จึงมีการโจมตีซ้ำหลายครั้งอย่างรุนแรง

ตอนนี้ Solidity v0.6.x เปิดตัวแล้ว [3] และนำมาซึ่งการเปลี่ยนแปลงมากมาย [4] แต่ 50% ของสัญญาอัจฉริยะที่สแกนยังไม่พร้อมสำหรับคอมไพเลอร์ Solidity v0.5.0 อีก 30% ของสัญญาอัจฉริยะใช้ไวยากรณ์ที่ล้าสมัย (เช่น: การใช้ sha3, Throw, ค่าคงที่ เป็นต้น) และ 83% ของสัญญามีปัญหาด้านข้อมูลจำเพาะ (pragmas) ในเวอร์ชันคอมไพเลอร์ที่ระบุ

หมายเหตุผู้แปล: Solidity 0.6 มีความชัดเจนในเชิงความหมายมากกว่า (เช่น เวอร์ชัน 0.6 ได้รับการอัปเกรดในแง่ของการสืบทอด [5]) ซึ่งช่วยให้คอมไพเลอร์ค้นหาปัญหาได้ทันเวลาและทำให้โค้ดปลอดภัยยิ่งขึ้น

แม้ว่าปัญหาการมองเห็น[6] จะไม่ปรากฏใน 10 อันดับแรกในปี 2018 หรือใน 10 อันดับแรกในปีนี้ แต่ปัญหาการมองเห็นที่เพิ่มขึ้น 48% นั้นเป็นสิ่งที่น่าสังเกต

ตารางด้านล่างเปรียบเทียบการเปลี่ยนแปลงระหว่างรายการคำถามที่พบบ่อย 10 อันดับแรกของปี 2018 และ 2020 ปัญหาเหล่านี้เรียงลำดับตามความรุนแรงและความชุก:

1. ยกเลิกการเลือกสายภายนอก

   if(!addr.send(1)) {
     revert()
   }

การโทรภายนอกที่ไม่ได้ตรวจสอบเป็นปัญหาที่พบบ่อยเป็นอันดับสามในรายการปัญหาด้านความปลอดภัย 10 อันดับแรกของ Solidity ประจำปี 2018 เมื่อสองรายการแรกได้รับการแก้ไขแล้ว การโทรภายนอกที่ไม่ได้ตรวจสอบคือปัญหาที่พบบ่อยที่สุดในรายการอัปเดตปี 2020

เมธอดการโทรพื้นฐานของ Solidity (เช่น address.call()) จะไม่เกิดข้อยกเว้น เมื่อพบข้อผิดพลาด ระบบจะส่งคืนค่า False แทน

และหากคุณใช้สัญญาเพื่อเรียก ExternalContract.doSomething() หาก doSomething() แสดงข้อยกเว้น ข้อยกเว้นจะยังคง "ฟองสบู่" และเผยแพร่ต่อไป

กรณีที่ไม่สำเร็จควรได้รับการจัดการอย่างชัดเจนโดยการตรวจสอบค่าที่ส่งคืน การถ่ายโอน ether ต่อไปนี้โดยใช้ addr.send() เป็นตัวอย่างที่ดี สิ่งนี้ใช้ได้กับการโทรภายนอกอื่นๆ ด้วย

   for(uint256 i=0; i< elements.length; i++) {
       // do something
   }

2. วงจรค่าใช้จ่ายสูง

High Cost Loops ย้ายจากสี่เป็นวินาทีในรายการ Solidity Security จำนวนสัญญาอัจฉริยะที่ได้รับผลกระทบจากปัญหานี้เพิ่มขึ้นเกือบ 30%

ทุกคนรู้ว่าการคำนวณบน Ethereum ต้องชำระเงิน ดังนั้น การลดการคำนวณที่ต้องใช้ในการดำเนินงานให้เสร็จสมบูรณ์จึงไม่ใช่แค่ปัญหาการปรับให้เหมาะสม (ประสิทธิภาพ) แต่ยังเป็นต้นทุนอีกด้วย

การวนซ้ำเป็นการดำเนินการที่มีราคาแพง นี่เป็นตัวอย่างที่ดี: ยิ่งองค์ประกอบในอาร์เรย์มีองค์ประกอบมากเท่าใด การวนซ้ำก็จำเป็นมากขึ้นเพื่อให้การวนซ้ำเสร็จสมบูรณ์ ในที่สุด การวนซ้ำไม่สิ้นสุดจะใช้ก๊าซที่มีอยู่จนหมด

หากผู้โจมตีสามารถมีอิทธิพลต่อความยาวของอาร์เรย์องค์ประกอบ โค้ดด้านบนจะทำให้เกิดการปฏิเสธบริการ (การดำเนินการไม่สามารถแยกออกจากลูปได้) ในการสแกนสัญญาอัจฉริยะ พบว่า 8% ของสัญญามีปัญหาการจัดการความยาวอาร์เรย์

3. เจ้าของที่มีอำนาจเหนือกว่า

   function calculateBonus(uint amount) returns (uint) {
       return amount/DELIMITER*BONUS;
   }

นี่เป็นปัญหาที่เกิดขึ้นในปัญหาด้านความปลอดภัยสิบอันดับแรกของ Soldiity ซึ่งส่งผลกระทบต่อประมาณ 16% ของสัญญา บางสัญญามีความเกี่ยวข้องอย่างใกล้ชิดกับเจ้าของ (Owner) และฟังก์ชันบางอย่างสามารถเรียกได้จากที่อยู่ของเจ้าของเท่านั้น ดังที่แสดงในตัวอย่างต่อไปนี้:

เฉพาะเจ้าของสัญญาเท่านั้นที่สามารถเรียกฟังก์ชัน doSomething() และ doSomethingElse() ได้: ฟังก์ชันแรกใช้มัณฑนากร onlyOwner ในขณะที่ฟังก์ชันหลังใช้อย่างชัดแจ้ง สิ่งนี้ก่อให้เกิดความเสี่ยงร้ายแรง: หากคีย์ส่วนตัวของเจ้าของถูกบุกรุก ผู้โจมตีจะสามารถควบคุมสัญญาได้

4. ปัญหาความแม่นยำทางเลขคณิต

   function transferTo(address dest, uint amount) {    
         require(tx.origin == owner) {      
                  dest.transfer(amount);  
         }
   }

ประเภทข้อมูลของ Solidity ค่อนข้างซับซ้อนเนื่องจากใช้เครื่องเสมือน 256 บิต (EVM[7]) Solidity ไม่มีเลขคณิตทศนิยม และชนิดข้อมูลที่มีขนาดน้อยกว่า 32 ไบต์จะถูกบรรจุลงในสล็อตขนาด 32 ไบต์เดียวกัน เมื่อคำนึงถึงสิ่งนี้ คุณควรคาดหวังปัญหาความแม่นยำของโปรแกรมต่อไปนี้:

ดังที่แสดงในตัวอย่างข้างต้น การหารที่ทำก่อนการคูณอาจมีข้อผิดพลาดในการปัดเศษมาก

5. ขึ้นอยู่กับ tx.origin

   for (uint i = border; i >= 0; i--) {  
         ans += i;
   }

สัญญาอัจฉริยะไม่ควรพึ่งพา tx.origin ในการตรวจสอบสิทธิ์ เนื่องจากสัญญาที่เป็นอันตรายอาจทำการโจมตีแบบคนกลางและทำให้เงินหมด ขอแนะนำให้ใช้ msg.sender แทน:<、>รายละเอียดของการโจมตี Tx Origin สามารถพบได้ในเอกสารประกอบของ Solidity [8] พูดง่ายๆ ก็คือ tx.origin เป็นบัญชีผู้ริเริ่มเริ่มต้นในห่วงโซ่การโทรของสัญญาเสมอ ในขณะที่ msg.sender เป็นตัวแทนของผู้โทรโดยตรง หากสัญญาสุดท้ายใน chain ใช้ tx.origin ในการตรวจสอบสิทธิ์ การเรียกสัญญาที่อยู่ตรงกลางของ chain จะสามารถระบายเงินจากสัญญาที่เรียกได้ เนื่องจากการตรวจสอบสิทธิ์ไม่ได้ตรวจสอบว่าใคร (msg.sender) เป็นคนสร้าง โทร.

6. โอเวอร์โฟลว์ (โอเวอร์โฟลว์ / อันเดอร์โฟลว์)

เครื่องเสมือน 256 บิตของ Solidity มีปัญหาโอเวอร์โฟลว์และอันเดอร์โฟลว์ (หมายเหตุผู้แปล: เนื่องจากผลลัพธ์เกินช่วงค่า จึงเรียกว่าโอเวอร์โฟลว์) ที่นี่ [9] มีการวิเคราะห์เฉพาะ นักพัฒนาควรใช้ความระมัดระวังเป็นพิเศษเมื่อใช้ประเภทข้อมูล uint ในเงื่อนไข for loop เนื่องจากอาจทำให้เกิดการวนซ้ำไม่สิ้นสุด:

ในตัวอย่างด้านบน เมื่อค่า i เป็น 0 ค่าถัดไปคือ 2^256 -1 ซึ่งทำให้เงื่อนไขเป็นจริงเสมอ นักพัฒนาควรลองใช้

, != และ == สำหรับการเปรียบเทียบ

   for (var i = 0; i < elements.length; i++) {
      // to something
   }

7. การหักเงินประเภทที่ไม่ปลอดภัย

ปัญหาดังกล่าวขยับขึ้นสองอันดับในรายการปัญหาด้านความปลอดภัย 10 อันดับแรกของ Solidity และตอนนี้ส่งผลกระทบต่อสัญญาอัจฉริยะมากกว่าเดิมมากกว่า 17%

Solidity รองรับการอนุมานประเภท แต่มีพฤติกรรมแปลก ๆ ตัวอย่างเช่น ตัวอักษร 0 ถูกอนุมานให้เป็นประเภทไบต์ แทนที่จะเป็นจำนวนเต็มที่คาดไว้ตามปกติ

ในตัวอย่างด้านล่าง ประเภทของ i ถูกอนุมานว่าเป็น uint8 เนื่องจากการเก็บค่า i เป็น uint8 ก็เพียงพอแล้ว แต่ถ้าอาร์เรย์องค์ประกอบมีมากกว่า 256 องค์ประกอบ โค้ดต่อไปนี้ล้น:

ขอแนะนำให้ประกาศประเภทข้อมูลอย่างชัดเจนเพื่อหลีกเลี่ยงพฤติกรรมและ/หรือข้อผิดพลาดที่ไม่คาดคิด

   if(!addr.send(1)) {    
      revert()
   }

หมายเหตุนักแปล: ตัวแปรนิยาม var ถูกลบออกใน Solidity 0.6 (ไม่มีการหักประเภทหลังจาก Solidity 0.6) หากคุณใช้คอมไพเลอร์ใหม่ ก็จะไม่มีปัญหา

8. การโอนไม่ถูกต้อง

ปัญหาดังกล่าวลดลงจากอันดับที่หกเป็นอันดับที่แปดในรายการปัญหาด้านความปลอดภัย 10 อันดับแรกของ Solidity และปัจจุบันมีผลกระทบน้อยกว่า 1% ของสัญญาอัจฉริยะ

   for (uint i = 0; i < users.lenghth; i++) {
      users[i].transfer(amount);
   }

มีหลายวิธีในการถ่ายโอนอีเธอร์ระหว่างสัญญา แม้ว่าคำแนะนำอย่างเป็นทางการคือให้ใช้ฟังก์ชัน addr.transfer(x) แต่เรายังคงพบสัญญาอัจฉริยะที่ยังคงใช้ฟังก์ชัน send():

โปรดทราบว่า addr.transfer(x) จะเพิ่มข้อยกเว้นโดยอัตโนมัติหากการถ่ายโอนไม่สำเร็จ ซึ่งจะช่วยลดปัญหาของการโทรภายนอกที่ไม่ได้ตรวจสอบครั้งแรกอีกครั้ง

9. การถ่ายโอนในวงจร

   if (timeHasCome == block.timestamp) {    
       winner.transfer(amount);
    }

เมื่อทำการถ่ายโอนอีเธอร์ในเนื้อความของลูป หากหนึ่งในการถ่ายโอนล้มเหลว (เช่น สัญญาไม่สามารถรับได้) ธุรกรรมทั้งหมดจะถูกย้อนกลับ<、>ในตัวอย่างนี้ ผู้โจมตีอาจใช้ประโยชน์จากพฤติกรรมนี้เพื่อดำเนินการโจมตีแบบปฏิเสธบริการ ป้องกันไม่ให้ผู้ใช้รายอื่นได้รับอีเธอร์

ในปี 2018 ปัญหาการพึ่งพาการประทับเวลาอยู่ในอันดับที่ห้า และสิ่งสำคัญคือต้องจำไว้ว่าสัญญาอัจฉริยะทำงานบนหลายโหนดในเวลาที่ต่างกัน Ethereum Virtual Machine (EVM) ไม่ได้ระบุเวลาของนาฬิกา และตัวแปร now (นามแฝงสำหรับ block.timestamp ) ที่มักใช้เพื่อรับการประทับเวลานั้นเป็นตัวแปรสภาพแวดล้อมที่นักขุดสามารถจัดการได้

สรุป

เนื่องจากคนขุดแร่สามารถจัดการกับตัวแปรสภาพแวดล้อมปัจจุบันได้ จึงสามารถใช้ได้เฉพาะในอสมการ >,

= และ <= ใช้ค่าของมัน

สรุป

ลิงค์แหล่งที่มา:securityboulevard.com

开发者
安全
ยินดีต้อนรับเข้าร่วมชุมชนทางการของ Odaily
กลุ่มสมาชิก:
https://t.me/Odaily_News
กลุ่มสนทนา:
https://t.me/Odaily_GoldenApe
บัญชีทางการ:
https://twitter.com/OdailyChina
สารบัญบทความ
คลังบทความของผู้เขียน
登链社区
อันดับบทความร้อน
Daily
Weekly
ดาวน์โหลดแอพ Odaily พลาเน็ตเดลี่
ให้คนบางกลุ่มเข้าใจ Web3.0 ก่อน
IOS
Android