(一) Reentrancy
合約在執行外部合約調用時被攻擊者劫持,重入遞迴呼叫合約內函數,著名的
DAO 事件 便是駭客利用此漏洞進行攻擊,最終導致以太坊的硬分叉。
(二) Arithmetic
通常分為整數上溢和整數下溢,以uint8 為例,只能存8 bit,意即0 到2⁸ -1 的數字(0–255),uint256能存0 到2²⁵⁶-1 的數字,以此類推。若試圖存256 至uint8 類型中,數字會變成0。下溢原理亦是。要防止整數溢出可以使用OpenZeppelin 所開發的
SafeMath 合約 對算術邏輯進行檢查,另外Solidity 在
v0.8.0 後預設會進行算數檢查,將不必另外引入函式庫。
(三) Short Addresses
處理ERC20 代幣的交易時,若未在帳戶地址長度上執行輸入驗證,EVM 檢測到缺少字節時將會
自動補零 ,會使攻擊者於交易時可以領走對方帳戶數十或是百倍的金額。若是在交易所進行交易,交易所需要對帳戶地址進行檢查,方能避免此類攻擊。
(四) Access Control
使用Solidity撰寫合約時可以使用private、public、external 和internal 對合約存取權限進行控管,而在使用和儲存變數可以使用memory、storage 和calldata。
• private
函數僅能在合約內部被調用。
• public
函數可以被任何帳號調用或呼叫,可以為外部合約或使用者、內部其他函數等。
• external
函數不能透過內部呼叫,僅能從外部調用。
• internal
一般用於合約繼承,父合約提供子合約進行呼叫或調用。
• memory
資料僅在函式執行期間存在,執行完畢後就被刪除。
• storage
storage 中的資料會寫入區塊鏈,只要合約存在就一直保留,修改狀態的話鏈上資料也會改變,這也是storage 使用成本高的原因。
• calldata
外部函數的引用必須使用calldata,也可用於其他變數的引用。只讀數據位置,可以確保資料不被修改,可以用來保存函數調用參數(之前僅能針對外部函數使用),
Solidity 0.6.9 版本 之後先前僅用於外部函數(external 修飾的函數)的 calldata 位置,現在可以在內部函數(internal 修飾的函數)使用。函數也可以返回使用calldata 宣告的陣列和結果,但是不能分配這些型別。
(五) Bad Randomness
在以太坊鏈上所有交易都是公開可以被查詢的,若在撰寫合約使用隨機數時沒有考慮到此特性,此漏洞可能被惡意使用者用以圖利。
以DASP 所提供的例子進行說明:
其利用private seed 與iteration 數字和keccak256 hash 函數結合,用以確定呼叫者是否獲勝,seed 的值可以透過檢視與該合約相關的交易紀錄來取得。得到 seed 值後 iteration 值也可以此方式計算獲取,整個 uint(keccak256(seed + iteration))的值便是可以被預測的。惡意的攻擊者可藉此使自己不斷贏得獎勵。
(六) Transaction Ordering Dependence (TOD)
亦被稱為Front-Running,在以太坊區塊鏈中,礦工藉由驗證交易獲得獎勵,手續費較高的交易會優先被完成,因此礦工的選擇會影響交易的順序,在以太坊鏈上任何人都可以看到他人未被驗證的交易內容,惡意攻擊者可以藉此竊取他人交易內容或合約,以更高的費用複製其交易,使礦工會選擇交易高的一方,便會產生TOD 之漏洞。
(七) Time Manipulation
合約若依賴於block.timestamp 是有被攻擊利用的風險的,礦工在對交易進行驗證時可以掌握時間,若礦工對使用時間戳進行控制的合約持有股份或有利可圖,可能造成有心的礦工(攻擊者)藉由選擇特定時間驗證圖利自己。
(八) Denial of Services
導致DOS 的狀況很多,包含out of gas、含有kill()的合約、可能意外阻斷服務之呼叫等等皆算此類。
經典的舉例如「
King Of Ether Throne」 ,「King Of Ether Throne」是一個能賺取以太幣以及可以留名的合約,使用者可以向合約投入金額換取成為國王的機會,若在14 天內都沒有被篡位就可以在專屬網站留下地址及帳戶名稱,若在14 天內被篡位,新任國王所投入的金額有部分金額會給遭篡位者作為補償。漏洞利用方式是以惡意合約作為篡位帳戶,在惡意合約的fallback() 內添加錯誤函數(如:revert()),即可阻斷合約,使後續其他使用者永遠都無法篡位成功。
(九) Unchecked Return Values For Low Level Calls
• call()
多用於調用外部合約函數,會回傳一個bool 值來表示外部調用成功或是失敗。
• delegatecall()、 staticcall()
允許合約於現有合約之環境進行上下文調用其它使用者之合約的程式碼,藉由回傳的bool 值來表示調用成功或者失敗。
• call{value: value}()
若只是透過fallback() 發送以太幣時,可以使用此方法。
• send()
在發送款項時若失敗會回傳false,有gas 消耗限制,僅提供2300 gas,但交易失敗狀態不會回滾。
• transfer()
若發送失敗交易狀態會回滾,有gas 消耗限制,僅提供2300 gas,在用以發送交易時是較為安全的寫法。
(十) Unknown Unknowns
由於智慧合約尚在發展階段,許多項目在發布前進行安全審計時漏洞無法被完全揭露,現有的驗證工具或方法也有侷限性,未來可能有新型態攻擊發生。
作者Alice目前為分散式金融應用安全從業人員,將來也會持續在
Vocus 以及
medium 上分享相關的研究,如果喜歡我的文章歡迎追蹤我的帳號喔!
另外,我已經加入由
趨勢科技防詐達人 所成立的方格子專題-《區塊鏈生存守則》,在那裡我會跟其他優質的創作者一起帶大家深入瞭解區塊鏈,並隨時向大家更新區塊鏈資安事件