上交所技術公司 朱立
該Email地址已收到反垃圾郵件插件保護。要顯示它您需要在瀏覽器中啟用JavaScript。

本文內容并非原創,而是來自對于以下兩篇博文的翻譯和解讀。
Blog 1:?http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal/
Blog 2:?http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour/

一、攻擊手法解密

由于攻擊者是在創建childDAO并將Ether持續轉入其中,這是目前唯一可行的提取Ether的機制,所以關注點從splitDAO函數開始。
splitDAO會創建childDAO(如果不存在的話),將分裂者擁有的Ether轉入childDAO中,支付任何滋生的Reward(目前應該是0)然后返回。
根據白皮書的設計,splitDAO的本意是要保護投票中處于弱勢地位的少數派防止他們被多數派通過投票的方式合法剝削。通過分裂出一個小規模的DAO,給予他們一個用腳投票的機制,同時仍然確保他們可以獲取分裂前進行的對外資助產生的可能收益。
但通往地獄的道路就這樣用鮮花鋪就了。

根據BLOG 2,在DAO.sol中,function splitDAO函數有這樣一行:


合約中,為msg.sender記錄的dao幣余額歸零、扣減dao幣總量totalSupply等等都發生在將發回msg.sender之后,這里是博主在Blog 1中指出的一個典型“反模式”。
接下來看withdrawRewardFor函數。目前github中所見最新代碼并非部署于Ethereum livenet的代碼,出問題的代碼原來的樣子博主說是這樣:

paidOut[_account] += reward 在問題代碼里面放在payOut函數調用之后,最新github代碼中已經被移到了payOut之前。
再看payOut函數調用。rewardAccount的類型是ManagedAccount,在ManagedAccount.sol中可以看到:

注意標深的這一行:對_recipient發出call調用,轉賬_amount個Wei,call調用默認會使用當前剩余的所有gas,此時call調用所執行的代碼步驟數可能很多,基本只受攻擊者所發消息的可用的gas限制。
把這些拼起來,黑客的攻擊手法就浮現了:

1)準備工作
黑客創建自己的黑客合約HC,該合約帶有一個匿名的fallback函數。根據solidity的規范,fallback函數將在HC收到Ether(不帶data)時自動執行。此fallback函數將通過遞歸觸發對THE DAO的splitDAO函數的多次調用(但不會次數太多以避免gas不夠),過程中還應該需要記錄當前調用深度以控制堆棧使用情況。

2)開始攻擊
黑客向THE DAO多次提交split proposal,并將每個proposal的recipient地址都設定為HC地址,同時設定適當的gasLimit。調用棧就會這樣:

提交split proposal
 ? ---> splitDAO函數(No. 1)
 ? ? ?---> withdrawRewardFor函數 (No. 1,黑客的dao余額和dao總量此時沒變!)
 ? ? ? ?---> papOut 函數(No. 1,向HC發送以太第一次)
 ? ? ? ? ? ---> HC的fallback函數 (No. 1)
 ? ? ? ? ? ? ?---->如果遞歸未達預設深度:調用split DAO函數(No. 2)
 ? ? ? ? ? ? ? ? ---> withdrawRewardFor函數(No. 2, 黑客dao余額等仍然沒變!)
 ? ? ? ? ? ? ? ? ? ?---> payOut函數(No. 2, 向HC發送以太第二次)
 ? ? ? ? ? ? ? ? ? ? ?---> HC的fallback函數 (No. 2)
 ? ? ? ? ? ? ? ? ? ? ? ? ---> (繼續遞歸)

由于堆棧深度和gas的限制,黑客可能需要預先準備多個不同賬戶分散持有dao,因為一次這樣的攻擊中,只有自上而下再逐層返回這樣一輪機會可以利用,返回后賬戶持有的dao就被清空無法再用。

3)轉款走人
轉入childDAO的錢在一定時間后根據原合約可以提取,黑客收割韭菜的時候到了。

二、防范和思考

從代碼看,本次攻擊得逞的因素有二:一是dao余額扣減和Ether轉賬這兩步操作的順序有誤,二是不受限制地執行未知代碼。
應用代碼順序方面,應先扣減dao的余額再轉賬Ether,因為dao的余額檢查作為轉賬Ether的先決條件,要求dao的余額狀況必須能夠及時反映最新狀況。在問題代碼實現中,盡管最深的遞歸返回并成功扣減黑客的dao余額,但此時對黑客dao余額的扣減已經無濟于事,因為其上各層遞歸調用中余額檢查都已成功告終,已經不會再有機會判斷最新余額了。
不受限制地執行未知代碼方面,雖然黑客當前是利用了solidity提供的匿名fallback函數,但這種對未知代碼的執行原則上可以發生在更多場景下,因為合約之間的消息傳遞完全類似于面向對象程序開發中的方法調用,而提供接口等待回調是設計模式中常見的手法,所以完全有可能執行一個未知的普通函數。
類似黑客事件不限于以太坊,很可能未來會在rootstock等平臺上重演。這是因為本次漏洞屬于應用層面,并不是以太坊本身的問題,甚至都不能歸咎于“圖靈完備”,因為這種攻擊即使在一個非圖靈完備的平臺上也可以奏效。總的來說,應用越復雜,應用出現安全問題的概率就越高
本次黑客事件帶給我們很多思考:在TRUST MACHINE上是否應該信任未知代碼?去中心化系統是否應該在設計之初就加入應急干預代碼以備“臨時停牌”?出現問題后在輿情掌控、信息公開等方面是否應該存在預案?項目在上線之前是否應該經過更長時間的社區審視和測試?
本次黑客事件不意味著以太坊乃至去中心化、區塊鏈的終結。雖然教訓深刻,但如果能夠汲取教訓,那么從中獲益的不僅僅是THE DAO, 以太坊,整個區塊鏈社區都將從中獲益。

原文鏈接:?http://ethfans.org/posts/114?from=groupmessage&isappinstalled=0