iT邦幫忙

0

Day 12:重入攻擊(概念與流程圖)

  • 分享至 

  • xImage
  •  

重入攻擊(Reentrancy)是智能合約最經典也最危險的漏洞之一。
它的本質不是複雜的密碼學,而是執行順序的問題──當合約在跟外部合約互動時,如果還沒把狀態更新就把控制權交出去,對方就可能在回呼(callback)中再次進入原合約,造成狀態被重複利用。

  1. 重入攻擊發生在:外部呼叫(transfer/call)發生在狀態更新之前。
  2. 危險點:balances[msg.sender] -= amount 寫在 call 之後。
  3. 防護原則:Checks → Effects → Interactions(先檢查、先更新、再互動),或使用互斥鎖(ReentrancyGuard)。

🔍 詳細流程圖(ASCII 示意,標出危險點)
User A (attacker) 請求 withdraw() -->
合約: require(balances[A] >= amt) <-- Checks
合約: (bool ok,) = A.call{value:amt}("") <-- External call (危險點)
↓ (在此外部 call 的 fallback 中)
A.fallback() 調用 target.withdraw() <-- 重入(re-enter)
合約: require(balances[A] >= amt) <-- 還沒被扣,條件仍成立
合約: (bool ok,) = A.call{value:amt}("") <-- 再次轉出
...
合約: balances[A] -= amt <-- Effects (但已太晚)

紅旗 (Danger): 當合約把資金先 call 出去而尚未更新 balances 時,攻擊者能在回調中再次呼叫 withdraw,重複取得資金。

🧾 Code示意(易讀版)

Vulnerable(易被攻擊的版本)
// vulnerable pseudo-code
mapping(address => uint) balances;

function withdraw(uint amount) public {
require(balances[msg.sender] >= amount); // check
(bool sent,) = msg.sender.call{value: amount}(""); // external call (危險)
require(sent);
balances[msg.sender] -= amount; // state update (晚了)
}

Fixed(Checks → Effects → Interactions)
// fixed pseudo-code
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount); // check
balances[msg.sender] -= amount; // effect: 先更新狀態
(bool sent,) = msg.sender.call{value: amount}(""); // interaction: 再外呼
require(sent);
}

Alternative: 用 ReentrancyGuard(互斥鎖)
contract UsesGuard is ReentrancyGuard {
function withdraw(uint amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool sent,) = msg.sender.call{value: amount}("");
require(sent);
}
}

✅ 為什麼 Checks → Effects → Interactions 有效?

把狀態(例如 user balance)先改掉,當攻擊者在回調再次呼叫 withdraw 時,require(balances[msg.sender] >= amount) 就會失敗,重入被阻止。互斥鎖則是從語意上禁止同一呼叫路徑被重複進入。

常見迷思(FAQ)
• 「只有 call() 會被攻擊嗎?」
任何會把控制權交出去的操作(call, transfer, send, 以及呼叫外部合約)都有風險;transfer 在新版 Solidity 因 gas 限制較安全,但仍不可掉以輕心。
• 「用 try/catch 可以解嗎?」
try/catch 處理呼叫錯誤,但若狀態仍未更新,try/catch 並不能防止重入;它只能控制錯誤處理流程。

重入攻擊雖然在原理上簡單,但後果殘酷。The DAO 當年的教訓就是:一個順序錯誤能讓整個系統付出天文數字代價。實作上,養成「先更新狀態再互動」或直接使用成熟的互斥鎖(如 OpenZeppelin 的 ReentrancyGuard)是最實際且有效的第一道防線。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言