今天,我們要來聊聊一個讓無數開發者跌破眼鏡的經典資安問題:整數溢位與下位
在日常生活中,我們都知道 0 - 1 = -1,或者 255 + 1 = 256。但在早期的以太坊智慧合約裡,這樣的簡單算術卻可能變成駭客無中生有、無限印鈔的超級魔法。今天,我們就來揭開這個隱藏在數字背後的駭客印鈔機。
什麼是整數溢位與下位?
在 Solidity 中,我們通常使用無號整數 (uint) 來代表代幣數量。無號整數意味著它沒有負數。
為了方便理解,我們用最小的型別 uint8(最多只能存 8 個位元,範圍是 0 到 255)來舉例:
溢位 :想像一台最高只能顯示 255 公里的機車里程表。當你騎到 255 公里,再往前騎 1 公里時,里程表不會變成 256,而是會「歸零」變成 0。
在 Solidity 中:uint8(255) + 1 = 0。
下位 :反過來,如果你現在里程是 0,試圖倒車(減 1),它也不會變成 -1,而是會「循環」到最大值 255。
在 Solidity 中:uint8(0) - 1 = 255。
真實慘案:BEC 代幣的 BatchOverflow 攻擊
這個特性聽起來像個小 Bug,但在 Web3 的世界裡,這可是核彈級的漏洞。2018 年,知名的 BeautyChain (BEC) 遭受了著名的「BatchOverflow」攻擊,導致幣價瞬間歸零。
我們來看看當時合約裡出問題的簡化版邏輯:
'''
// 假設這是一個批量轉帳函數
function batchTransfer(address[] memory receivers, uint256 value) public {
// 計算總共需要轉出多少代幣
uint256 amount = uint256(receivers.length) * value;
// 檢查發送者餘額是否大於等於 amount
require(balances[msg.sender] >= amount, "Insufficient balance");
// 執行轉帳...
balances[msg.sender] -= amount;
for (uint256 i = 0; i < receivers.length; i++) {
balances[receivers[i]] += value;
}
}
'''
駭客的攻擊手法:駭客傳入了兩個接收地址 (receivers.length = 2),並且將 value 設為一個超級大的天文數字(例如 2^{255})。當合約執行 2 * (2^{255}) 時,結果超過了 uint256 能容納的最大極限(2^{256}-1),於是發生了整數溢位!算出來的 amount 瞬間「歸零」變成了 0。接下來的 require 檢查 balances[msg.sender] >= 0 當然輕鬆通過(因為駭客本來就沒有錢)。但在底下的 for 迴圈中,合約卻真的把那個超級大的 value 轉給了駭客指定的地址!駭客就這樣憑空印出了無數的代幣。
救世主:OpenZeppelin 的 SafeMath
為了對抗這個災難,在 Solidity 0.8.0 版本之前,業界的標準作法是強制引入 OpenZeppelin 開發的 SafeMath 函式庫。
SafeMath 的原理非常直白:在每次做加減乘除時,都先檢查一次結果合不合理。例如加法的底層實作:
'''
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow"); // 如果加完之後數字反而變小了,立刻報錯!
return c;
}
'''
以前的開發者必須把程式碼寫成 balances[msg.sender] = balances[msg.sender].sub(amount) 來避免下位問題。
Solidity 0.8.0 之後的進化與 unchecked 陷阱
好消息是,從 Solidity 0.8.0 開始,編譯器終於內建了溢位/下位檢查!如果發生上述情況,交易會自動 Revert。我們再也不需要手動引入 SafeMath,程式碼也變得更加簡潔。
但是,資安危機真的解除了嗎?
並沒有。因為內建的安全檢查會消耗額外的 Gas 費。很多高階 DeFi 開發者為了極致省錢,會刻意使用 unchecked 區塊來關閉安全檢查:
'''
// 開發者確信這裡不會溢位,為了省 Gas 把檢查關掉
unchecked {
balances[msg.sender] -= amount;
}
'''
身為資安稽核人員,只要在合約中看到 unchecked 關鍵字,這絕對是我們雷達上閃閃發亮的紅燈!我們必須極度嚴格地審視外部參數是否有可能被操控,進而在這個區塊內引發下位攻擊。
從 BatchOverflow 事件到 Solidity 的底層進化,整數溢位與下位完美示範了「語言特性」是如何演變成「資安漏洞」的。