智能合約安全審計入門系列之拒絕服務:可以導致智能合約無法正常使用的代碼邏輯錯誤,兼容性錯誤或調用深度過大(區塊鏈虛擬機的特性)的安全問題。
By:小白
背景概述
在上次的文章中我們學習了智能合約中獲取隨機數常用的幾種方式,以及他們的優缺點,還介紹了偽隨機數在智能合約中會造成哪些危害,這次我們來了解一個既存在於傳統網絡安全又存在於智能合約安全中的問題——拒絕服務。
前置知識
傳統網絡安全拒絕服務攻擊(DoS):DoS 是Denial of service 的簡稱,即拒絕服務,任何對服務的干涉,使得其可用性降低或者失去可用性均稱為拒絕服務。常見的針對網絡協議造成拒絕服務的攻擊手段大致有以下幾種:SYN Flood,IP 欺騙性攻擊,UDP 洪水攻擊,Ping 洪流攻擊,Teardrop 攻擊,Land 攻擊,Smurf 攻擊,Fraggle 攻擊等。
智能合約拒絕服務攻擊:可以導致智能合約無法正常使用的代碼邏輯錯誤,兼容性錯誤或調用深度過大(區塊鏈虛擬機的特性)的安全問題。智能合約中的拒絕服務攻擊手法就相對比較簡單,包括但不限於以下三種:
1、基於代碼邏輯的拒絕服務攻擊:這種類型的拒絕服務攻擊一般情況下是因為合約代碼邏輯的不嚴謹造成的,最典型的就是當合約中存在對傳入的映射或數組循環遍歷的邏輯且沒有限制傳入的映射或數組的長度時攻擊者可以通過傳入超長的映射或者數組進行循環遍歷而大量消耗Gas 從而該筆交易的Gas 溢出,最後使得智能合約暫時或永久不可操作。
2、基於外部調用的拒絕服務攻擊:這種拒絕服務攻擊是建立在合約中對外部調用處理不當導致的。例如智能合約中存在基於外部函數執行的結來改變合約狀態且沒有對交易一直失敗的情況做出處理,攻擊者會利用這個特點故意使交易失敗,智能合約則會一直重複這筆失敗的交易從而造成智能合約邏輯卡在這裡不能繼續執行,最後使得智能合約暫時或永久不可操作。
3、基於運營管理的拒絕服務攻擊:這種拒絕服務攻擊就是建立在後期運營情況下,例如在智能合約中通常會存在以Owner 賬戶作為管理員角色,該角色通常會持有很高的權限,例如開啟或暫停轉賬功能,當Owner 角色操作失誤或私鑰丟失可能會受到非主觀意義上的拒絕服務攻擊。
漏洞示例
通過前置知識相信大家已經對拒絕服務這類攻擊有一定的了解了,在觸發拒絕服務攻擊的三種手法中最典型的就是基於外部調用的拒絕服務攻擊。下面我們就通過一段典型的代碼示例來帶大家深入了解:
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract KingOfEther { address public king; uint public balance; function claimThrone() external payable { require(msg.value > balance, “Need to pay more to become the king”); (bool sent, ) = king.call{value: balance}(“”); require(sent, “Failed to send Ether”); balance = msg.value; king = msg.sender; }}
漏洞分析
我們可以看到上述合約的目的是選取“以太之王”,玩家可以通過claimThrone() 合約中打入大於之前用戶的任意數量的以太幣來競爭“以太之王”的稱號,當打入的以太幣高於之前玩家時打入的以太幣留在合約中並獲得“以太之王”稱號,之前玩家的以太幣會原路退回。
我們可以看到,生成新王和退回舊王的邏輯是在同一函數內完成的,並且claimThrone() 中還檢查了退款的返回值sent,下面我們來結合這個特點來完成攻擊。
攻擊合約
注:以下合約代碼邏輯以及攻擊場景僅作演示示例,請勿胡亂聯想。
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract Attack { KingOfEther kingOfEther; constructor(KingOfEther _kingOfEther) { kingOfEther = KingOfEther(_kingOfEther); } function attack() public payable { kingOfEther.claimThrone{value: msg.value}(); }}
首先我們先來分析攻擊流程:
1. Alice 部署KingOfEther 合約。
2. Alice 調用KingOfEther.claimThrone() 發送1 個以太到KingOfEther 合約中成為“以太之王”。
3. 高富帥Bob 調用KingOfEther.claimThrone() 發送2 個以太到KingOfEther 合約中成為新王。
4. Alice 收到1 個以太幣的退款。
5. Eve 使用KingOfEther 的地址部署攻擊合約Attack。
6. Eve 調用Attack.attack() 向KingOfEther 合約中發送3 個以太。
7. Attack 合約成為新王。
8. 高富帥Bob 覺得不服,再次調用KingOfEther.claimThrone() 向KingOfEther 合約中發送了20 個以太展現自己的“鈔能力”。
9. Bob 發現自己的交易一直被revert,無法成為新王。至此,Eve 的攻擊使KingOfEther 合約永久失效,Attack 合約成為了永遠的“以太之王”。
高富帥Bob 覺得不可思議,為啥自己這麼有錢還不能稱王呢?我們來看看到底是為什麼。
當Bob 調用KingOfEther.claimThrone() 發送20 個以太到KingOfEther 合約時會觸發KingOfEther.claimThrone() 的退款邏輯,將之前Eve 通過Attack.attack() 向KingOfEther 合約中發送的3 個以太原路退回到Attack 合約。我們再來看Attack 合約,該合約中沒有實現payable 的fallback() 所以不能接收以太幣,這將導致KingOfEther.claimThrone() 的退款邏輯一直失敗,退款返回值sent 將一直為false 無法通過require(sent, “Failed to send Ether”) 檢查一直被revert。因為只要觸發退款就會被revert 導致KingOfEther 合約中繼Attack 合約後無人能成為新王,Eve 成功完成了拒絕服務攻擊。
修復建議
作為開發者
1. 在智能合約開發中應當注意處理連續失敗的情況,例如將可能出現失敗的外部調用邏輯異步處理。
2. 在使用call 進行外部調用以及使用循環和遍歷時應當注意Gas 消耗。
3. 避免對單個角色過度授權的情況,處理合約權限時應做到合理的權限劃分,對擁有權限的角色使用多簽錢包管理,防止由於私鑰洩漏導致權限丟失。
下面是針對上面漏洞合約的修復示例:
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract KingOfEther { address public king; uint public KingValue; mapping(address => uint) public balances; function claimThrone() external payable { balances[msg.sender] += msg.value; require(balances[msg.sender] > balance, “Need to pay more to become the king”); KingValue = balances[msg.sender]; king = msg.sender; } function withdraw() public { require(msg.sender != king, “Current king cannot withdraw”); uint amount = balances[msg.sender]; balances[msg.sender] = 0; (bool sent, ) = msg.sender.call{value: amount}(“”); require(sent, “Failed to send Ether”); }}
可以看到修復合約中添加了balances 映射,它記錄了每個人向合約中打入以太的總數量相較於之前合約的優勢是玩家失去王位後可以追加以太重新獲得王位。修復版本的關鍵點是將退款邏輯作異步處理,需要玩家手動調用withdraw() 來自助退款,就算遇到惡意玩家拒收以太也只能影響到自己,不會再造成之前的拒絕服務了。
作為審計者
內部合約進行分析:
1. 注意合約中是否存在邏輯上的錯誤導致影響了可用性。
2. 注意是否存在由於虛擬機調用深度過大導致的DoS(深度最大1024)。
3. 重點關注在代碼邏輯中是否存在大量消耗Gas 的邏輯。
外部合約進行分析:
1. 關注與外部合約進行交互的時候沒有考慮好兼容性的問題,如:未處理TRC20-USDT 的返回值的兼容性,導致代幣被鎖定。
2. 重點檢查有沒有判斷外部合約調用的返回值是否符合預期的效果。
對權限管理進行分析:
在審計中需要檢查和確認所有函數方法的可見性及調用權限,需要結合項目方提供設計文檔在審計中根據設計文檔中的描述一一確認權限。如果發現過度授權或權限劃分不清晰的需要與項目方交流改進方法,並且與項目方溝通運營操作的流程,確保流程上能夠避免合約在運營的時候管理員操作失誤或配置錯誤的情況。
注:本文參考於《Solidity by Example》
參考鏈接:https://solidity-by-example.org/hacks/randomness
展開全文打開碳鏈價值APP 查看更多精彩資訊