北京時間 2019年05月07日,區塊鏈安全公司 Zeppelin 對以太坊上的 DeFi 明星項目 MakerDAO 發出安全預警,宣稱其治理合約存在安全漏洞,希望已
北京時間 2019年05月07日,區塊鏈安全公司 Zeppelin 對以太坊上的 DeFi 明星項目 MakerDAO 發出安全預警,宣稱其治理合約存在安全漏洞,希望已鎖倉參與投票的用戶盡快解鎖 MKR 提并出。MakerDAO 的開發者 Maker 公司亦確認了漏洞存在,并上線了新的治理合約,并宣稱漏洞已修復。
該安全威脅曝出后,PeckShield 全程追蹤了 MKR 代幣的轉移情況,并多次向社區發出預警,呼吁 MKR 代幣持有者立即轉移舊合約的 MKR 代幣。截止目前,絕大多數的 MKR 代幣已經完成了轉移,舊治理合約中尚有 2,463 個 MKR 代幣(價值約 128萬美元)待轉移。
05月07日當天,經 PeckShield 獨立研究發現,確認了該漏洞的存在(我們命名為 itchy DAO),具體而言:由于該治理合約實現的投票機制(vote(bytes32))存在某種缺陷,允許投票給尚不存在的 slate(但包含有正在投票的提案)。 等用戶投票后,攻擊者可以惡意調用 free()退出,達到減掉有效提案的合法票數,并同時鎖死投票人的 MKR 代幣。
次日05月08日,PeckShield 緊急和 Maker 公司同步了漏洞細節,05月10日凌晨,MakerDAO 公開了新版合約。Zeppelin 和 PeckShield 也各自獨立完成了對其新合約的審計,確定新版本修復了該漏洞。
在此我們公布漏洞細節與攻擊手法,也希望有引用此第三方庫合約的其它 DApp 能盡快修復。
細節
在 MakerDAO 的設計里,用戶是可以通過投票來參與其治理機制,詳情可參照 DAO 的 FAQ。
以下是關于 itchy DAO 的細節,用戶可以通過 lock / free 來將手上的 MKR 鎖定并投票或是取消投票:
在 lock 鎖定 MKR 之后,可以對一個或多個提案 (address 數組) 進行投票:
注意到這里有兩個 vote 函數,兩者的傳參不一樣 (address 數組與 byte32),
而 vote(address[] yays) 最終亦會調用 vote(bytes32 slate),其大致邏輯如下圖所示:
簡單來說,兩個 vote 殊途同歸,最后調用 addWeight 將鎖住的票投入對應提案:
可惜的是,由于合約設計上失誤,讓攻擊者有機會透過一系列動作,來惡意操控投票結果,甚致讓鎖定的 MKR 無法取出。
這里我們假設有一個從未投過票的黑客打算開始攻擊:
1. 調用 lock() 鎖倉 MKR,此時 deposits[msg.sender] 會存入鎖住的額度。
2. 此時黑客可以線下預先算好要攻擊的提案并預先計算好哈希值,拿來做為步驟 3 的傳參,因為 slate 其實只是 address 數組的 sha3。
這里要注意挑選的攻擊目標組合必須還不存在于 slates[] 中 (否則攻擊便會失敗),黑客亦可以自己提出一個新提案來加入組合計算,如此便可以確定這個組合必定不存在。
3. 調用 vote(bytes32 slate),因為 slate 其實只是 address 數組的 sha3,黑客可以線下預先算好要攻擊的提案后傳入。
這時因為 votes[msg.sender] 還未賦值,所以 subWeight() 會直接返回。接下來黑客傳入的 sha3(slate) 會存入 votes[msg.sender],之后調用 addWeight()。從上方的代碼我們可以看到,addWeight() 是透過 slates[slate] 取得提案數組,此時 slates[slate] 獲取到的一樣是未賦值的初始數組,所以 for 循環不會執行(由于 yays.length = 0)
4. 調用 etch() 將目標提案數組傳入。注意 etch() 與兩個 vote() 函數都是 public,所以外部可以隨意調用。這時 slates[hash] 就會存入對應的提案數組。
5. 調用 free() 解除鎖倉。這時會分成以下兩步:
· deposits[msg.sender] = sub(deposits[msg.sender], wad)
解鎖黑客在 1. 的鎖倉
· subWeight(wad, votes[msg.sender])
從對應提案中扣掉黑客的票數,然而從頭到尾其實攻擊者都沒有真正為它們投過票
從上面的分析我們了解,黑客能透過這種攻擊造成以下可能影響:
一、惡意操控投票結果
二、因為黑客預先扣掉部份票數,導致真正的投票者有可能無法解除鎖倉
時間軸