Contents

Reentrancy Attack คืออะไรและทำงานอย่างไร?

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

การโจมตีแบบ Reentrant เกี่ยวข้องกับการใช้ประโยชน์จากช่องโหว่ในแอปพลิเคชันซอฟต์แวร์ที่อนุญาตให้เรียกใช้รหัสที่เป็นอันตรายซ้ำ ๆ ซึ่งนำไปสู่การเข้าถึงหรือควบคุมระบบโดยไม่ได้รับอนุญาต การโจมตีเหล่านี้มักเกิดขึ้นเมื่อแอปพลิเคชันล้มเหลวในการจัดการทรัพยากรหน่วยความจำหรือการตรวจสอบความถูกต้องของข้อมูลเข้าอย่างเหมาะสม ทำให้ผู้โจมตีสามารถจัดการกับโฟลว์ของโปรแกรมและได้รับสิทธิ์ขั้นสูง เพื่อลดความเสี่ยงของการโจมตีย้อนกลับ นักพัฒนาต้องแน่ใจว่ามีการทดสอบและตรวจสอบโค้ดอย่างละเอียด ใช้แนวปฏิบัติในการเข้ารหัสที่ปลอดภัย และปฏิบัติตามแนวทางความปลอดภัยมาตรฐานอุตสาหกรรม เช่น OWASP (Open Web Application Security Project) นอกจากนี้ ควรใช้การอัปเดตและแพตช์เป็นประจำทันทีเพื่อแก้ไขช่องโหว่ที่ทราบและป้องกันภัยคุกคามที่เกิดขึ้นใหม่

Reentrancy Attack คืออะไร?

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

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

/th/images/dao-reentrancy-attack.jpg เครดิตรูปภาพ: Etherscan

หนึ่งในการแฮ็กบล็อกเชนที่น่าอับอายที่สุด นั่นคือการแฮ็ก Ethereum DAO ซึ่งครอบคลุมโดย Coindesk เป็นการโจมตีแบบ reentrancy ที่นำไปสู่การสูญเสีย eth มูลค่ากว่า 60 ล้านดอลลาร์ และเปลี่ยนเส้นทางของ cryptocurrency ที่ใหญ่เป็นอันดับสองโดยพื้นฐาน

การโจมตี Reentrancy ทำงานอย่างไร

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

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

ภายในขอบเขตของข้อตกลงอัจฉริยะ การดำเนินการจะดำเนินการในลักษณะต่อไปนี้:

นักแสดงที่ประสงค์ร้ายพบข้อบกพร่องในสัญญาอัจฉริยะที่มีชื่อว่า"X"ซึ่งเปิดโอกาสให้แสวงหาผลประโยชน์

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

การระงับการดำเนินการตามสัญญาของ X นั้นขึ้นอยู่กับความจำเป็นในการโต้ตอบกับเหตุการณ์ภายนอก ส่งผลให้การทำงานหยุดชะงักชั่วคราวหรือเกิดความล่าช้า

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

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

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

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

ตัวอย่างของการโจมตี Reentrancy

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

 // Vulnerable contract with a reentrancy vulnerability

pragma solidity ^0.8.0;

contract VulnerableContract {
    mapping(address => uint256) private balances;

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

    function withdraw(uint256 amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance");
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
        balances[msg.sender] -= amount;
    }
}

VulnerableContract อนุญาตให้ผู้ใช้ทำการฝาก Ether (ETH) โดยใช้ฟังก์ชัน ฝาก ในขณะเดียวกัน ผู้ใช้จะได้รับอนุญาตให้ดึงเงินที่ฝากไว้ผ่านคุณสมบัติ ถอน น่าเสียดายที่การดำเนินการหลังนี้มีความเสี่ยงต่อช่องโหว่ reentrant ภายในฟังก์ชัน ถอน เอง โดยเฉพาะอย่างยิ่ง เมื่อเริ่มต้นคำขอถอนเงิน สัญญาจะจ่ายล่วงหน้าตามจำนวนเงินที่ระบุให้กับผู้รับที่กำหนดของผู้ใช้ก่อนที่จะแก้ไขยอดเงินในบัญชี การดำเนินการเชิงรุกนี้เผยให้เห็นข้อบกพร่องด้านความปลอดภัยที่อาจถูกทำร้ายโดยผู้ประสงค์ร้ายที่ต้องการใช้ประโยชน์จากสถานการณ์

ตัวอย่างสมมุติฐานของสัญญาอัจฉริยะที่เป็นอันตรายอาจนำเสนอเพื่อตรวจสอบ

 // Attacker's contract to exploit the reentrancy vulnerability

pragma solidity ^0.8.0;

interface VulnerableContractInterface {
    function withdraw(uint256 amount) external;
}

contract AttackerContract {
    VulnerableContractInterface private vulnerableContract;
    address private targetAddress;

    constructor(address _vulnerableContractAddress) {
        vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);
        targetAddress = msg.sender;
    }

    // Function to trigger the attack
    function attack() public payable {
        // Deposit some ether to the vulnerable contract
        vulnerableContract.deposit{value: msg.value}();

        // Call the vulnerable contract's withdraw function
        vulnerableContract.withdraw(msg.value);
    }

    // Receive function to receive funds from the vulnerable contract
    receive() external payable {
        if (address(vulnerableContract).balance >= 1 ether) {
            // Reenter the vulnerable contract's withdraw function
            vulnerableContract.withdraw(1 ether);
        }
    }

    // Function to steal the funds from the vulnerable contract
    function withdrawStolenFunds() public {
        require(msg.sender == targetAddress, "Unauthorized");
        (bool success, ) = targetAddress.call{value: address(this).balance}("");
        require(success, "Transfer failed");
    }
} 

เมื่อเริ่มการโจมตี:

คลาส AttackerContract ในระหว่างการเริ่มต้น ได้รับที่อยู่ของ VulnerableContract ผ่านตัวสร้างและกำหนดให้กับฟิลด์ vulnerableContract

ผู้โจมตีเรียกใช้ฟังก์ชัน"ฝากเงิน"ของสัญญาอัจฉริยะที่เป็นเป้าหมาย โดยฉีด Ether (ETH) ตามจำนวนที่ระบุตามด้วยการเรียกใช้ฟังก์ชัน"ถอน"ทันทีภายในอินสแตนซ์สัญญาเดียวกัน

ฟังก์ชันการถอนภายใน VulnerableContract จะถ่ายโอน Ethereum (ethereum) จำนวนหนึ่งไปยังบัญชีของผู้กระทำความผิดก่อนที่จะปรับยอดคงเหลือในบัญชี อย่างไรก็ตาม เนื่องจากสัญญาอัจฉริยะของผู้บุกรุกยังคงถูกระงับตลอดการร้องขอจากภายนอก การดำเนินการยังไม่สมบูรณ์ ดำเนินการ

เมื่อมีการเรียกใช้ฟังก์ชัน receive ภายใน AttackerContract จะเริ่มทำงานโดยเป็นผลมาจาก VulnerableContract ที่ส่ง Ether ไปยังสัญญานี้ระหว่างการโทรจากภายนอก

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

ทำซ้ำขั้นตอนที่สามถึงห้าซ้ำๆ ตราบเท่าที่ VulnerableContract ยังคงมีเงินทุนอยู่ และสัญญาของผู้โจมตีไม่สะสม Ether ในปริมาณที่มากเกินไป

ท้ายที่สุดแล้ว ผู้บุกรุกอาจใช้ฟังก์ชันของฟังก์ชัน ถอน StolenFunds'ภายในบริบทของ AttackerContract ` ซึ่งจะเป็นการแยกและเก็บรักษาทรัพย์สินใด ๆ ที่ถูกรวบรวมโดยสัญญาที่ถูกบุกรุกระหว่างการดำเนินการ

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

วิธีป้องกัน Reentrancy Attack

เพื่อลดความเสี่ยงของการโจมตีผู้ที่กลับเข้ามาใหม่บนสัญญาอัจฉริยะของเรา จำเป็นอย่างยิ่งที่เราจะต้องปฏิบัติตามแนวทางที่กำหนดไว้สำหรับการพัฒนาสัญญาที่ปลอดภัย โดยเฉพาะอย่างยิ่ง เราจะนำรูปแบบ"การตรวจสอบผลกระทบ-การโต้ตอบ"(CEI) มาใช้ตามที่แสดงในข้อมูลโค้ดต่อไปนี้

 // Secure contract with the "checks-effects-interactions" pattern

pragma solidity ^0.8.0;

contract SecureContract {
    mapping(address => uint256) private balances;
    mapping(address => bool) private isLocked;

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

    function withdraw(uint256 amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance");
        require(!isLocked[msg.sender], "Withdrawal in progress");
        
        // Lock the sender's account to prevent reentrancy
        isLocked[msg.sender] = true;

        // Perform the state change
        balances[msg.sender] -= amount;

        // Interact with the external contract after the state change
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");

        // Unlock the sender's account
        isLocked[msg.sender] = false;
    }
} 

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

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

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

/th/images/ethereum-wallet-1.jpg เครดิตรูปภาพ: Ivan Radic/Flickr

โดยทั่วไปแล้ว มีสามประเภทหลักของการโจมตีย้อนกลับที่สามารถจำแนกตามลักษณะที่พวกมันถูกโจมตี

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

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

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

การป้องกันการโจมตีซ้ำจำเป็นต้องใช้มาตรการตอบโต้ที่แตกต่างกันสำหรับการนำเสนอการโจมตีดังกล่าวแบบต่างๆ

อยู่อย่างปลอดภัยจากการโจมตี Reentrancy

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

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