SC Denial of Service

拒絕服務攻擊(Denial of Service, DoS)在 Solidity 智能合約中是指攻擊者通過利用合約漏洞,耗盡資源(如 gas、CPU 週期或儲存空間),使合約無法正常運作,從而阻止用戶與合約交互。常見的 DoS 攻擊類型包括:

  1. Gas 耗盡攻擊:攻擊者創建需要大量 gas 的交易,使合約無法完成執行。
  2. 重入攻擊:利用合約調用順序漏洞,反覆調用函數以耗盡資源或存取未授權資金。
  3. 區塊 Gas 限制攻擊:通過提交大量高 gas 消耗的交易,佔用區塊的 gas 限制,阻礙其他合法交易被處理。

這些攻擊的目標是使合約不可用,影響其功能、用戶體驗及相關去中心化應用(dApp)的運作。

範例(存在漏洞的合約)

以下是一個易受 DoS 攻擊的智能合約範例:

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

contract Solidity_DOS {
    address public king;
    uint256 public balance;

    function claimThrone() external payable {
        require(msg.value > balance, "Need to pay more to become the king");

        //If the current king has a malicious fallback function that reverts, it will prevent the new king from claiming the throne, causing a Denial of Service.
        (bool sent,) = king.call{value: balance}("");
        require(sent, "Failed to send Ether");

        balance = msg.value;
        king = msg.sender;
    }
}

合約功能

  • 這是一個「國王遊戲」合約,允許用戶通過支付比當前 balance 更高的金額(msg.value)來成為新的「國王」(king)。
  • 當新用戶成功支付後,舊的 king 會收到退款(當前的 balance),然後 kingbalance 更新為新用戶的地址和支付金額。

漏洞分析

  • 問題:在 claimThrone 函數中,合約使用 king.call{value: balance}("") 向舊的 king 發送以太幣。
    • 如果舊的 king 是一個惡意合約,且其回退函數(receivefallback)故意回滾(例如,通過 revert 或無限迴圈耗盡 gas),則 call 會失敗,導致 require(sent, "Failed to send Ether") 觸發交易回滾。
    • 這意味著新用戶無法成為 king,因為舊 king 的惡意行為阻止了合約狀態的更新。
  • DoS 風險:惡意 king 可以持續阻止任何新用戶更新 king,使合約無法正常運作。

影響

DoS 攻擊可能導致以下後果:

  1. 合約不可用:用戶無法與合約交互,影響關鍵功能(如資金轉移、狀態更新)。
  2. 財務損失:在管理資金或資產的 dApp 中,DoS 攻擊可能導致資金無法提取或操作中斷,造成經濟損失。
  3. 聲譽損害:用戶對合約及其相關平台的信任下降,可能導致用戶流失和商業機會減少。

修復方法

為防止 DoS 攻擊,開發者應採取以下措施:

處理外部調用失敗

  • 使用非阻塞的外部調用處理方式,例如異步處理或將資金轉移延後到獨立函數,確保合約不因單一失敗而癱瘓。

避免高 gas 消耗

  • 謹慎使用 call(因為它允許接收者執行任意程式碼)以及迴圈或遍歷操作,避免 gas 耗盡。
  • 使用 transfersend(有固定 gas 限制)代替 call,減少惡意回退函數的影響。

分散權限

  • 避免將過多權限集中於單一角色(如 king)。使用多重簽名(Multi-Signature)或分散式權限管理,降低單點故障風險。

狀態更新優先

  • 在執行外部調用(如轉帳)前更新合約狀態,防止因外部調用失敗導致狀態不一致。

修復後的範例合約

以下是修復了 DoS 漏洞的合約:

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

contract Solidity_DOS {
    address public king;
    uint256 public balance;

    // Use a safer approach to transfer funds, like transfer, which has a fixed gas stipend.
    // This avoids using call and prevents issues with malicious fallback functions.
    function claimThrone() external payable {
        require(msg.value > balance, "Need to pay more to become the king");

        address previousKing = king;
        uint256 previousBalance = balance;

        // Update the state before transferring Ether to prevent reentrancy issues.
        king = msg.sender;
        balance = msg.value;

        // Use transfer instead of call to ensure the transaction doesn't fail due to a malicious fallback.
        payable(previousKing).transfer(previousBalance);
    }
}

修復內容

使用 transfer 代替 call

  • transfer 提供固定 gas 限制(2300 gas),防止接收者的回退函數執行複雜邏輯或故意回滾。
  • 如果轉帳失敗,交易會自動回滾,確保合約狀態一致。

狀態更新優先

  • 在執行 transfer 前,先更新 kingbalance,避免因轉帳失敗導致狀態未更新(也減少重入風險)。

安全性提升

  • 即使舊 king 的回退函數惡意回滾,transfer 的失敗不會阻止新 king 登基,解決 DoS 問題。

總結

拒絕服務攻擊(DoS)通過耗盡資源或利用外部調用失敗使智能合約無法使用。範例中的漏洞合約因使用 call 且未妥善處理惡意回退函數,允許舊 king 阻止新用戶更新狀態,導致合約不可用。修復方法包括使用安全的 transfer、優先更新狀態、避免高 gas 操作和分散權限。修復後的合約通過 transfer 和狀態優先更新解決了 DoS 風險,確保合約功能正常且用戶可順利交互。開發者應謹慎設計外部調用並進行全面測試,以防止 DoS 攻擊影響合約的可靠性和安全性。