เราเพิ่งเห็นว่าช่องโหว่เล็กๆ นำไปสู่การสูญเสียทางการเงิน (ขนาดต่างๆ กัน) ในลักษณะที่คล้ายคลึงกันของสัญญาอัจฉริยะที่พัฒนาขึ้นบน Solidity มีแนวโน้มที่จะถูกโจมตีทั้งที่รู้จักและไม่รู้จัก ผู้บุกรุกใช้ประโยชน์จากข้อบกพร่องและช่องโหว่เพื่อดูสัญญาที่ชาญฉลาดและจัดการเพื่อดำเนินการโจมตี เราขอนำเสนอรายการข้อผิดพลาดทั่วไป 5 อันดับแรกที่มักพบในภาษาโปรแกรม Solidity
ที่ QuillAudits เราปฏิบัติตามวิธีการแบบปรับตัวเพื่อรับส่วนสำคัญของการแฮ็กทุกครั้ง และใช้การเรียนรู้เกี่ยวกับสัญญาอัจฉริยะในอนาคตเพื่อหลีกเลี่ยงภัยคุกคามที่อาจเกิดขึ้น
ข้อผิดพลาดในภาษาโปรแกรม solidity
1. ไม่ได้เลือกการโทรภายนอก
เรากำลังดึงปัญหานี้ตั้งแต่แรกเพราะเป็นหนึ่งในข้อผิดพลาดของ Solidity ที่พบได้บ่อยที่สุด โดยทั่วไป การส่งอีเทอร์ไปยังบัญชีภายนอกใดๆ จะดำเนินการผ่าน โอนย้าย() การทำงาน. นอกเหนือจากนี้ สองฟังก์ชันที่ใช้กันอย่างแพร่หลายที่สุดในการโทรออกภายนอก ได้แก่ เรียก()และ ส่ง(), ที่นี่ส่วนใหญ่ เรียก() ฟังก์ชันนี้ถูกใช้อย่างกว้างขวางเพื่อดำเนินการเรียกภายนอกที่หลากหลายโดยนักพัฒนา
แม้ว่า เรียก() และ ส่ง() ฟังก์ชันส่งคืนค่าบูลีนที่ระบุว่าการโทรสำเร็จหรือไม่ ดังนั้น ในกรณีนี้ ถ้าฟังก์ชันใด ๆ เรียก() or ส่ง() ล้มเหลวในการทำงานพวกเขาจะย้อนกลับด้วย เท็จ ดังนั้น หากผู้พัฒนาไม่ตรวจสอบมูลค่าที่ส่งคืนมา มันจะกลายเป็นหลุมพราง
ช่องโหว่
พิจารณาตัวอย่างด้านล่าง:
สัญญาล็อตโต้{
boolpublic payedOut = เท็จ;
ที่อยู่ผู้ชนะสาธารณะ;
ไม่ใช่สาธารณะ winAmount;
// … ฟังก์ชันพิเศษที่นี่
ฟังก์ชัน sendToWinner()สาธารณะ{
ต้องการ (!payedOut);
ผู้ชนะ.ส่ง(winAmount);
จ่ายออก =จริง;
}
ฟังก์ชันถอนซ้ายโอเวอร์()สาธารณะ{
ต้องการ (จ่ายออก);
msg.sender.send(นี้.สมดุล);
}
}
ในสัญญาที่คล้ายล็อตโต้ข้างต้น เราสามารถสังเกตได้ว่า a ผู้ชนะ ที่ได้รับ จำนวนเงินที่ชนะ ของอีเทอร์เหลือเพียงเล็กน้อยเพื่อถอนออกจากตัวแทนภายนอกใดๆ
ที่นี่ หลุมพรางสำหรับสัญญาอยู่ที่บรรทัดที่ [11] โดยที่ a ส่ง ใช้โดยไม่มีการตรวจสอบความถูกต้องของการตอบสนอง ในตัวอย่างข้างต้น a ผู้ชนะ ซึ่งการทำธุรกรรมล้มเหลว (ไม่ว่าจะโดยการขาดก๊าซหรือถ้าเป็นสัญญาที่จงใจโยนในฟังก์ชั่นทางเลือก) อนุญาต จ่ายออก ที่จะตั้งค่าเป็น จริง ไม่ว่าธุรกรรมของอีเธอร์จะสำเร็จหรือไม่ก็ตาม ในกรณีนี้ ผู้โจมตีสามารถถอนตัว ผู้ชนะ ชนะผ่าน ถอนซ้ายโอเวอร์ ฟังก์ชัน
แนวทางของ QuillAudit
ทีมนักพัฒนาภายในของเราจัดการกับจุดบกพร่องนี้ด้วยการใช้ [โอนย้าย] ทำหน้าที่แทน [ส่ง] ฟังก์ชัน เนื่องจาก [โอน] จะเปลี่ยนกลับหากธุรกรรมภายนอกกลับรายการ และหากคุณใช้ [send] ให้ตรวจสอบค่าที่ส่งคืนเสมอ
หนึ่งในแนวทางที่แข็งแกร่งที่เราปฏิบัติตามคือการใช้ [รูปแบบการถอนตัว] ในที่นี้ เราแยกฟังก์ชันการส่งข้อมูลภายนอกออกจากส่วนที่เหลือของโค้ดเบสอย่างมีเหตุมีผล และวางภาระของธุรกรรมที่อาจล้มเหลวกับผู้ใช้ปลายทาง เนื่องจากเขาเป็นผู้เรียกใช้ฟังก์ชันการถอนเงิน
2. กลับเข้ามาใหม่
สัญญาอัจฉริยะ Ethereum เรียกและใช้รหัสจากสัญญาภายนอกอื่น ๆ และในการดำเนินการนี้ สัญญาจะต้องส่งการโทรภายนอก การโทรภายนอกเหล่านี้มีความเสี่ยงและมีแนวโน้มที่จะถูกโจมตี การโจมตีดังกล่าวเกิดขึ้นเมื่อเร็วๆ นี้ในกรณีของการแฮ็ก DAO
ช่องโหว่
ผู้โจมตีทำการโจมตีดังกล่าวเมื่อสัญญาส่งอีเธอร์ไปยังที่อยู่ที่ไม่รู้จัก ในกรณีนี้ ผู้โจมตีสามารถสร้างสัญญาที่ที่อยู่ภายนอกซึ่งมีโค้ดที่เป็นอันตรายในฟังก์ชันทางเลือก และโค้ดที่เป็นอันตรายนี้จะถูกเรียกใช้เมื่อสัญญาส่งอีเธอร์ไปยังที่อยู่นี้
ความจริง: คำว่า 'Reentrancy' ถูกสร้างขึ้นจากข้อเท็จจริงที่ว่าเมื่อสัญญาที่เป็นอันตรายภายนอกเรียกใช้ฟังก์ชันเหนือสัญญาที่มีช่องโหว่ จากนั้นเส้นทางการเรียกใช้โค้ดจะ 'ป้อน' อีกครั้ง
พิจารณาตัวอย่างด้านล่าง เป็นห้องนิรภัย Ethereum ที่อนุญาตให้ผู้ฝากถอนได้เพียง 1 อีเธอร์ต่อสัปดาห์
สัญญา EtherStore {
uint256 การถอนสาธารณะลิมิต = 1 อีเธอร์;
การทำแผนที่ (ที่อยู่ => uint256) สาธารณะ LastWithdrawTime;
การทำแผนที่ (ที่อยู่ => uint256) ยอดคงเหลือสาธารณะ;
ฟังก์ชัน depositFunds() เจ้าหนี้ภายนอก {
ยอดคงเหลือ[msg.sender] += msg.value;
}
ฟังก์ชั่นถอนเงิน (uint256 _weiToWithdraw) สาธารณะ {
ต้องการ (ยอดคงเหลือ [msg.sender] >= _weiToWithdraw);
//จำกัดการถอน
ต้องการ (_weiToWithdraw <=drawalLimit);
//จำกัดเวลาที่อนุญาตให้ถอนได้
ต้องการ (ตอนนี้ >= lastWithdrawTime[msg.sender] + 1 สัปดาห์);
ต้องการ(msg.sender.call.value(_weiToWithdraw)());
ยอดคงเหลือ[msg.sender] -= _weiToWithdraw;
lastWithdrawTime[msg.sender] = ตอนนี้;
}
}
ในสัญญาข้างต้น เรามีหน่วยงานสาธารณะสองแห่งคือ [depositFunds] และ [withdrawFunds] [depositFunds] ใช้เพื่อเพิ่มยอดเงินของผู้ส่ง ในขณะที่ [withdrawFunds] ระบุจำนวนเงินที่จะถอน ในกรณีนี้ จะประสบความสำเร็จหากจำนวนเงินที่จะถอนออกน้อยกว่า 1 อีเธอร์
หลุมพรางที่นี่อยู่ในแนวที่ [17] ที่มีการถ่ายโอนอีเทอร์เกิดขึ้น ผู้โจมตีสามารถสร้างสัญญาที่เป็นอันตรายโดยมีที่อยู่สัญญาของ [EtherStores] เป็นพารามิเตอร์ตัวสร้างเพียงตัวเดียว สิ่งนี้จะทำให้ [etherStore] เป็นตัวแปรสาธารณะ ดังนั้นจึงมีแนวโน้มที่จะถูกโจมตีมากขึ้น
แนวทางของ QuilllAudit
เราปฏิบัติตามเทคนิคต่างๆ เพื่อหลีกเลี่ยงช่องโหว่การกลับเข้ามาใหม่ที่อาจเกิดขึ้นในสัญญาอัจฉริยะ วิธีแรกและดีที่สุดคือการใช้ฟังก์ชัน [โอน] ในตัวเมื่อถ่ายโอนอีเธอร์ไปยังสัญญาภายนอกใดๆ
ประการที่สอง สิ่งสำคัญคือต้องตรวจสอบให้แน่ใจว่าการเปลี่ยนแปลงตรรกะทั้งหมดในตัวแปรสถานะควรทำก่อนที่จะส่งอีเธอร์ออกจากสัญญา ในตัวอย่าง [EtherStore] ควรวางบรรทัด [18] และ [19] ไว้ข้างหน้าบรรทัด [17]
เทคนิคที่สามสามารถใช้เพื่อป้องกันการโทรกลับ ผ่านการแนะนำของ mutex เป็นการเพิ่มตัวแปรสถานะที่จะล็อกสัญญาระหว่างการใช้รหัส
3. การมองเห็นเริ่มต้น
มีตัวระบุการมองเห็นสำหรับฟังก์ชันที่เราใช้ใน Solidity และกำหนดวิธีการเรียก เป็นการมองเห็นที่กำหนดการเรียกใช้ฟังก์ชัน ภายนอกโดยผู้ใช้ โดยสัญญาที่ได้รับอื่นๆ เฉพาะภายในหรือภายนอกเท่านั้น ให้เราดูว่าการใช้ตัวระบุการมองเห็นที่ผิดพลาดสามารถทำให้เกิดช่องโหว่ขนาดใหญ่ในสัญญาอัจฉริยะได้อย่างไร
ช่องโหว่
โดยค่าเริ่มต้น การมองเห็นของฟังก์ชันจะเป็น [สาธารณะ] ดังนั้น ผู้ใช้ภายนอกจึงสามารถเรียกใช้ฟังก์ชันต่างๆ ได้โดยไม่ต้องมองเห็นเฉพาะ ข้อผิดพลาดเกิดขึ้นเมื่อนักพัฒนาลืมระบุการมองเห็นในฟังก์ชันที่ควรเป็นส่วนตัว (หรือสามารถเรียกได้ภายในสัญญา) ตัวอย่างเช่น;
สัญญา HashForEther {
ฟังก์ชั่นถอนเงินชนะ () {
// ผู้ชนะหากอักขระฐานสิบหก 8 ตัวสุดท้ายของที่อยู่เป็น 0
ต้องการ (uint32(msg.sender) == 0);
_sendWinnings();
}
ฟังก์ชัน _sendWinnings () {
msg.sender.transfer(นี่.บาลานซ์);
}
}
สัญญาข้างต้นเป็นเกมทายที่อยู่ง่าย ๆ ในที่นี้ เราจะเห็นว่าไม่มีการระบุการมองเห็นของฟังก์ชัน โดยเฉพาะอย่างยิ่งฟังก์ชัน [ _sendWinnings] คือ [สาธารณะ] (โดยค่าเริ่มต้น) ดังนั้นจึงสามารถเรียกสิ่งนี้ผ่านที่อยู่ใดก็ได้เพื่อขโมยเงินรางวัล
แนวทางของ QuillAudit
ทีมงานภายในของเราประกอบด้วยนักพัฒนาที่มีประสบการณ์ซึ่งมักจะปฏิบัติตามแนวปฏิบัติด้านการตรวจสอบที่ดีที่สุด ควรมีการระบุการมองเห็นฟังก์ชันอย่างชัดเจนในที่นี้ แม้ว่าจะต้องเปิดเผยต่อสาธารณะก็ตาม ก็ควรกล่าวถึง
4. การปกป้องการใช้ตัวสร้าง
โดยทั่วไป คอนสตรัคเตอร์จะเรียกว่าฟังก์ชันพิเศษที่ใช้ในการทำงานที่สำคัญและมีสิทธิพิเศษในขณะที่เริ่มต้นสัญญา ก่อน Solidity [v0.4.22] คอนสตรัคเตอร์มีชื่อเดียวกับที่ใช้โดยสัญญาที่มีอยู่ ตอนนี้ ให้พิจารณากรณีที่ชื่อสัญญามีการเปลี่ยนแปลงในระหว่างขั้นตอนการพัฒนา แต่ชื่อตัวสร้างยังคงเหมือนเดิม ช่องโหว่นี้ยังช่วยให้ผู้โจมตีเข้าถึงสัญญาอัจฉริยะของคุณได้โดยง่าย
ช่องโหว่
มันสามารถนำไปสู่ผลลัพธ์ที่รุนแรงได้หากชื่อสัญญาถูกแก้ไขแต่ชื่อของตัวสร้างไม่เปลี่ยนแปลง ตัวอย่างเช่น:
สัญญา OwnerWallet {
ที่อยู่เจ้าของสาธารณะ;
// ตัวสร้าง
ฟังก์ชั่น ownerWallet (ที่อยู่ _owner) สาธารณะ {
เจ้าของ = _owner;
}
// รั้งท้าย. รวบรวมอีเธอร์
ฟังก์ชั่น () เจ้าหนี้ {}
ฟังก์ชั่นถอน () สาธารณะ {
ต้องการ(msg.sender == เจ้าของ);
msg.sender.transfer(นี่.บาลานซ์);
}
}
ในสัญญาข้างต้น เราจะเห็นได้ว่ามีเพียงเจ้าของเท่านั้นที่สามารถถอนอีเทอร์ได้ผ่านการเรียกใช้ฟังก์ชัน [ถอนเงิน] ในที่นี้ ช่องโหว่เกิดขึ้นเนื่องจากชื่อ Constructor ต่างจากสัญญา (ตัวอักษรตัวแรกต่างกัน!) ดังนั้นผู้บุกรุกจึงสามารถเรียกใช้ฟังก์ชัน [ownerWallet] และให้สิทธิ์ตนเองในฐานะเจ้าของ จากนั้นจึงถอนอีเธอร์ทั้งหมดในสัญญาโดยโทร [ถอนออก]
แนวทางของ QuillAudit
เราปฏิบัติตามเวอร์ชัน [0.4.22] ของคอมไพเลอร์ Solidity รุ่นนี้ได้แนะนำคำหลัก [constructor] ซึ่งต้องการชื่อของฟังก์ชันให้ตรงกับชื่อสัญญา
5. การรับรองความถูกต้องของ Tx.Origin
ที่นี่ [Tx.Origin] เป็นตัวแปรส่วนกลางของ Solidity ซึ่งมีที่อยู่ของบัญชีที่เรียกใช้การโทรหรือธุรกรรมในขั้นต้น ตัวแปรนี้ใช้ตรวจสอบสิทธิ์ไม่ได้ เนื่องจากจะทำให้สัญญาเสี่ยงต่อการโจมตีแบบฟิชชิง
ช่องโหว่
สัญญาที่อนุญาตผู้ใช้ผ่านตัวแปร [tx.origin] จะถูกโจมตีจากภายนอกซึ่งนำผู้ใช้ให้ดำเนินการตรวจสอบสิทธิ์ในสัญญาที่ผิดพลาด พิจารณาตัวอย่างด้านล่าง:
สัญญา Phishable {
ที่อยู่เจ้าของสาธารณะ;
ตัวสร้าง (ที่อยู่ _owner) {
เจ้าของ = _owner;
}
ฟังก์ชั่น () เจ้าหนี้ภายนอก {} // รวบรวม ether
ฟังก์ชั่นถอนทั้งหมด (ที่อยู่ _recipient) สาธารณะ {
ต้องการ (tx.origin == เจ้าของ);
_recipient.transfer(นี้.ยอดดุล);
}
}
ที่บรรทัดที่ [11] สัญญาอนุญาตฟังก์ชัน [withdrawAll] ด้วยความช่วยเหลือของ [tx.origin]
แนวทางของ QuillAudit
โดยทั่วไป เราจะหลีกเลี่ยงการใช้ [tx.origin] เพื่อการอนุญาตในสัญญาอัจฉริยะ แม้ว่าการใช้ [tx.origin] จะไม่ได้ห้ามโดยเด็ดขาด แต่ก็มีบางกรณีการใช้งานที่เฉพาะเจาะจง เราสามารถใช้ [tx.origin] เพื่อปฏิเสธสัญญาภายนอกจากการเรียกสัญญาปัจจุบัน มันสามารถดำเนินการได้ด้วย [require] ของแบบฟอร์ม [require(tx.origin == msg.sender)] ทำเพื่อหลีกเลี่ยงการเรียกสัญญากลางเพื่อเรียกสัญญาปัจจุบันซึ่งจำกัดสัญญาไว้ที่ที่อยู่แบบไม่มีรหัสปกติ
สุดท้ายห่อขึ้น
เราได้ครอบคลุมข้อผิดพลาดทั่วไปห้าประการในภาษา Solidity แล้ว ในขณะที่พัฒนาสัญญาอัจฉริยะ เราต้องไม่ลืมว่าสัญญาเหล่านี้จะไม่เปลี่ยนรูปแบบโดยการออกแบบ ซึ่งหมายความว่าเมื่อเราสร้างสัญญาเหล่านี้แล้ว จะไม่มีทางแก้ไขซอร์สโค้ดได้
นี่เป็นความท้าทายที่ยิ่งใหญ่สำหรับนักพัฒนาในการใช้ประโยชน์จากการทดสอบความปลอดภัยและเครื่องมือตรวจสอบที่มีอยู่ก่อนใช้งาน
การค้นพบภัยคุกคามที่เป็นอันตรายที่อาจเกิดขึ้นกับสัญญาอัจฉริยะ และความเสี่ยงที่เราได้กล่าวมาข้างต้นนั้นดำเนินการด้วยวิธีที่ไม่เหมือนใครและแข็งแกร่งโดยทีมผู้เชี่ยวชาญด้านการตรวจสอบภายในของเรา พวกเราที่ QuillAudits พยายามอย่างเต็มที่ในการวิจัยด้านความปลอดภัยเพื่อให้สัญญาของคุณอัปเดตด้วยแนวทางปฏิบัติด้านความปลอดภัยของซอฟต์แวร์ทั้งหมดเพื่อให้สัญญาของคุณปลอดภัย
ติดต่อ QuillHash
ด้วยการปรากฏตัวของอุตสาหกรรมหลายปี ขนนกแฮช ได้ส่งมอบโซลูชันระดับองค์กรไปทั่วโลก QuillHash พร้อมทีมผู้เชี่ยวชาญคือบริษัทพัฒนาบล็อกเชนชั้นนำที่ให้บริการโซลูชั่นอุตสาหกรรมต่างๆ รวมถึงองค์กร DeFi หากคุณต้องการความช่วยเหลือในการตรวจสอบสัญญาอัจฉริยะ โปรดติดต่อผู้เชี่ยวชาญของเรา ที่นี่!
ติดตาม QuillHash สำหรับการอัปเดตเพิ่มเติม
ที่มา: https://blog.quillhash.com/2021/06/04/top-5-common-errors-in-solidity-programming-language/
- 11
- ลงชื่อเข้าใช้
- ความได้เปรียบ
- ทั้งหมด
- การตรวจสอบบัญชี
- การยืนยันตัวตน
- การอนุญาต
- ที่ดีที่สุด
- blockchain
- Bug
- เป็นโรคจิต
- โทรศัพท์
- กรณี
- ก่อให้เกิด
- ท้าทาย
- รหัส
- ร่วมกัน
- บริษัท
- สัญญา
- สัญญา
- ปัจจุบัน
- DAO
- Defi
- ออกแบบ
- ผู้พัฒนา
- นักพัฒนา
- พัฒนาการ
- Enterprise
- อีเทอร์
- ethereum
- เหตุการณ์
- ผู้เชี่ยวชาญ
- ทางการเงิน
- ชื่อจริง
- ปฏิบัติตาม
- ฟอร์ม
- ฟรี
- ฟังก์ชัน
- อนาคต
- เกม
- GAS
- เหตุการณ์ที่
- ยิ่งใหญ่
- สับ
- โปรดคลิกที่นี่เพื่ออ่านรายละเอียดเพิ่มเติม
- สรุป ความน่าเชื่อถือของ Olymp Trade?
- HTTPS
- ใหญ่
- รวมทั้ง
- อุตสาหกรรม
- IT
- ภาษา
- นำ
- ชั้นนำ
- Line
- รายการ
- การจับคู่
- อื่นๆ
- เจ้าของ
- ปะ
- แบบแผน
- ฟิชชิ่ง
- การโจมตีแบบฟิชชิ่ง
- กำหนด
- นำเสนอ
- ส่วนตัว
- การเขียนโปรแกรม
- สาธารณะ
- การดึง
- การวิจัย
- คำตอบ
- REST
- ปลอดภัย
- ความปลอดภัย
- บริการ
- ชุด
- ง่าย
- เล็ก
- สมาร์ท
- สัญญาสมาร์ท
- สัญญาสมาร์ท
- So
- ซอฟต์แวร์
- ความแข็งแรง
- โซลูชัน
- สถานะ
- ความสำเร็จ
- การทดสอบ
- ที่มา
- ภัยคุกคาม
- เวลา
- ด้านบน
- ชั้น 5
- การทำธุกรรม
- การทำธุรกรรม
- us
- ผู้ใช้
- ความคุ้มค่า
- หกคะเมน
- ความชัดเจน
- ช่องโหว่
- ความอ่อนแอ
- อ่อนแอ
- สัปดาห์
- WHO
- ภายใน
- ปี