SC Unchecked External Calls

未檢查的外部調用(Unchecked External Calls)是一種安全漏洞,指合約在對另一個合約或地址進行外部調用時,未正確檢查調用的結果。在以太坊中,當一個合約調用另一個合約時,被調用的合約可能會靜默失敗而不拋出異常。如果調用合約未檢查返回值的話,可能會錯誤地假設調用成功,即使實際上並非如此。這可能導致合約狀態不一致,並成為攻擊者可利用的漏洞。

範例(存在漏洞的合約)

以下是一個存在未檢查外部調用漏洞的智能合約範例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Solidity_UncheckedExternalCall {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function forward(address callee, bytes memory _data) public {
        callee.delegatecall(_data);
    }
}

漏洞分析

  • forward 函數:該函數使用 delegatecall 對用戶提供的地址 (callee) 進行調用,但未檢查調用的返回結果。
  • 問題
    • 如果 delegatecall 失敗,合約不會察覺,並繼續執行,導致狀態可能不一致。
    • 此外,該函數允許任意地址執行任意程式碼,且未進行驗證,這增加了額外風險(詳見注意事項)。

影響

未檢查的外部調用可能導致以下嚴重後果:

  1. 交易失敗:預期操作未成功完成,但合約繼續執行,造成資金損失。
  2. 狀態不一致:合約假設調用成功,導致邏輯錯誤,並暴露更多漏洞供攻擊者利用。
  3. 資金損失與進一步攻擊:攻擊者可能利用失敗的調用來操控合約狀態,竊取資金或造成邏輯混亂。

修復方法

為避免未檢查的外部調用漏洞,開發者應採取以下措施:

  1. 優先使用 transfer():代替 send(),因為 transfer() 在外部調用失敗時會自動回滾交易。
  2. 檢查返回值:在使用 send()call() 等函數時,始終檢查返回的布林值(例如 bool success),如果為 false,則處理錯誤(如使用 require 拋出異常)。
  3. 額外驗證:在調用前檢查被調用地址的有效性,例如確保它是合約地址(程式碼大小大於零)。

修復後的範例合約

以下是修復了未檢查外部調用漏洞的合約範例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; 

contract Solidity_CheckedExternalCall {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function forward(address callee, bytes memory _data) public {
        // Ensure that delegatecall succeeds
        (bool success, ) = callee.delegatecall(_data);
        require(success, "Delegatecall failed");  // Check the return value to handle failure
    }
}

修復內容

1.檢查返回值

  • forward 函數中,使用 (bool success, ) = callee.delegatecall(_data); 捕獲返回結果,並以 require(success) 確保調用成功。
  • 如果調用失敗,交易會回滾,避免狀態不一致。

2.安全性提升

  • 這防止了靜默失敗,但注意該合約仍有其他潛在弱點(詳見注意事項)。

注意事項 (Caveats)

上述兩個合約除了未檢查返回值外,還存在其他弱點:

  1. 授權委託給被調用者:驗證邏輯委託給被調用的合約 (callee),該合約可能未限制 msg.sender(例如,檢查是否為 owner)。通常,forward 函數應自行進行某種形式的驗證。
  2. 用戶提供的地址 (callee):這允許用戶提供任意地址,導致在當前合約上下文中執行任意程式碼,可能修改如 owner 等變數。這特別危險,因為 forward 未進行驗證。
  3. 未檢查地址是否為合約:如果 callee 是沒有程式碼的地址,delegatecall 會靜默成功但無效。通常,forward 應進行基本檢查,例如驗證被調用合約的程式碼大小大於零(使用 extcodesize)。

這些注意事項強調,僅檢查返回值不足以完全安全;需結合其他防護措施,如權限控制和輸入驗證。

總結

未檢查的外部調用是智能合約中常見的漏洞,可能導致交易失敗、狀態不一致和資金損失。透過檢查返回值、使用安全的轉帳方法(如 transfer())以及額外驗證被調用地址,開發者可以有效修復此問題。然而,如注意事項所述,合約設計需全面考慮多層安全,包括授權和輸入檢查,以避免更廣泛的漏洞。修復後的合約展示了如何通過返回值檢查來提升安全性,但開發時仍需全面審計。